Support more cases for date parsing
We didn't have support for DateTimeOffset, nor support for parsing formats with nullable DateTime. Added E2E tests for bind + DateTime/DateTimeOffset. Added E2E tests for 'manual bind' with number and date field. These can't use `@bind` without a compiler update. This PR will add the runtime support we need, so we can update the compiler.
This commit is contained in:
parent
1e6739f108
commit
80f71bcd1f
|
|
@ -46,6 +46,9 @@ namespace Microsoft.AspNetCore.Components
|
|||
public static partial class BindMethods
|
||||
{
|
||||
public static string GetValue(System.DateTime value, string format) { throw null; }
|
||||
public static string GetValue(System.DateTimeOffset value, string format) { throw null; }
|
||||
public static string GetValue(System.DateTimeOffset? value, string format) { throw null; }
|
||||
public static string GetValue(System.DateTime? value, string format) { throw null; }
|
||||
public static T GetValue<T>(T value) { throw null; }
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
|
||||
|
|
@ -124,6 +127,8 @@ namespace Microsoft.AspNetCore.Components
|
|||
public static partial class EventCallbackFactoryBinderExtensions
|
||||
{
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<bool> setter, bool existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset> setter, System.DateTimeOffset existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset> setter, System.DateTimeOffset existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime> setter, System.DateTime existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime> setter, System.DateTime existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<decimal> setter, decimal existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
|
|
@ -131,7 +136,10 @@ namespace Microsoft.AspNetCore.Components
|
|||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<int> setter, int existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<long> setter, long existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<bool?> setter, bool? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset?> setter, System.DateTimeOffset? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTimeOffset?> setter, System.DateTimeOffset? existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime?> setter, System.DateTime? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<System.DateTime?> setter, System.DateTime? existingValue, string format, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<decimal?> setter, decimal? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<double?> setter, double? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<int?> setter, int? existingValue, System.Globalization.CultureInfo culture = null) { throw null; }
|
||||
|
|
|
|||
|
|
@ -23,6 +23,25 @@ namespace Microsoft.AspNetCore.Components
|
|||
value == default ? null
|
||||
: (format == null ? value.ToString() : value.ToString(format));
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static string GetValue(DateTime? value, string format) =>
|
||||
value == default ? null
|
||||
: (format == null ? value.ToString() : value.Value.ToString(format));
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static string GetValue(DateTimeOffset value, string format) =>
|
||||
value == default ? null
|
||||
: (format == null ? value.ToString() : value.ToString(format));
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static string GetValue(DateTimeOffset? value, string format) =>
|
||||
value == default ? null
|
||||
: (format == null ? value.ToString() : value.Value.ToString(format));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
|
|
@ -26,6 +25,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
public static class EventCallbackFactoryBinderExtensions
|
||||
{
|
||||
private delegate bool BindConverter<T>(object obj, CultureInfo culture, out T value);
|
||||
private delegate bool BindConverterWithFormat<T>(object obj, CultureInfo culture, string format, out T value);
|
||||
|
||||
// Perf: conversion delegates are written as static funcs so we can prevent
|
||||
// allocations for these simple cases.
|
||||
|
|
@ -261,9 +261,16 @@ namespace Microsoft.AspNetCore.Components
|
|||
}
|
||||
|
||||
private static BindConverter<DateTime> ConvertToDateTime = ConvertToDateTimeCore;
|
||||
private static BindConverterWithFormat<DateTime> ConvertToDateTimeWithFormat = ConvertToDateTimeCore;
|
||||
private static BindConverter<DateTime?> ConvertToNullableDateTime = ConvertToNullableDateTimeCore;
|
||||
private static BindConverterWithFormat<DateTime?> ConvertToNullableDateTimeWithFormat = ConvertToNullableDateTimeCore;
|
||||
|
||||
private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, out DateTime value)
|
||||
{
|
||||
return ConvertToDateTimeCore(obj, culture, format: null, out value);
|
||||
}
|
||||
|
||||
private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, string format, out DateTime value)
|
||||
{
|
||||
var text = (string)obj;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
|
|
@ -272,17 +279,27 @@ namespace Microsoft.AspNetCore.Components
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
|
||||
if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = converted;
|
||||
return true;
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, out DateTime? value)
|
||||
{
|
||||
return ConvertToNullableDateTimeCore(obj, culture, format: null, out value);
|
||||
}
|
||||
|
||||
private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, string format, out DateTime? value)
|
||||
{
|
||||
var text = (string)obj;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
|
|
@ -291,14 +308,82 @@ namespace Microsoft.AspNetCore.Components
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
|
||||
if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static BindConverter<DateTimeOffset> ConvertToDateTimeOffset = ConvertToDateTimeOffsetCore;
|
||||
private static BindConverterWithFormat<DateTimeOffset> ConvertToDateTimeOffsetWithFormat = ConvertToDateTimeOffsetCore;
|
||||
private static BindConverter<DateTimeOffset?> ConvertToNullableDateTimeOffset = ConvertToNullableDateTimeOffsetCore;
|
||||
private static BindConverterWithFormat<DateTimeOffset?> ConvertToNullableDateTimeOffsetWithFormat = ConvertToNullableDateTimeOffsetCore;
|
||||
|
||||
private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset value)
|
||||
{
|
||||
return ConvertToDateTimeOffsetCore(obj, culture, format: null, out value);
|
||||
}
|
||||
|
||||
private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset value)
|
||||
{
|
||||
var text = (string)obj;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = converted;
|
||||
return true;
|
||||
if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset? value)
|
||||
{
|
||||
return ConvertToNullableDateTimeOffsetCore(obj, culture, format: null, out value);
|
||||
}
|
||||
|
||||
private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset? value)
|
||||
{
|
||||
var text = (string)obj;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
value = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
|
||||
{
|
||||
value = converted;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ConvertToEnum<T>(object obj, CultureInfo culture, out T value) where T : struct, Enum
|
||||
|
|
@ -602,26 +687,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
DateTime existingValue,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTime>(factory, receiver, setter, culture, ConvertToDateTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime?> setter,
|
||||
DateTime? existingValue,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTime?>(factory, receiver, setter, culture, ConvertToNullableDateTime);
|
||||
return CreateBinderCore<DateTime>(factory, receiver, setter, culture, format: null, ConvertToDateTimeWithFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -642,45 +708,127 @@ namespace Microsoft.AspNetCore.Components
|
|||
string format,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
// Avoiding CreateBinderCore so we can avoid an extra allocating lambda
|
||||
// when a format is used.
|
||||
Action<UIChangeEventArgs> callback = (e) =>
|
||||
{
|
||||
DateTime value = default;
|
||||
var converted = false;
|
||||
try
|
||||
{
|
||||
value = ConvertDateTime(e.Value, culture, format);
|
||||
converted = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return CreateBinderCore<DateTime>(factory, receiver, setter, culture, format, ConvertToDateTimeWithFormat);
|
||||
}
|
||||
|
||||
// See comments in CreateBinderCore
|
||||
if (converted)
|
||||
{
|
||||
setter(value);
|
||||
}
|
||||
};
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime?> setter,
|
||||
DateTime? existingValue,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTime?>(factory, receiver, setter, culture, format: null, ConvertToNullableDateTimeWithFormat);
|
||||
}
|
||||
|
||||
static DateTime ConvertDateTime(object obj, CultureInfo culture, string format)
|
||||
{
|
||||
var text = (string)obj;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.RoundtripKind, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.Parse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.RoundtripKind);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime?> setter,
|
||||
DateTime? existingValue,
|
||||
string format,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTime?>(factory, receiver, setter, culture, format, ConvertToNullableDateTimeWithFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTimeOffset> setter,
|
||||
DateTimeOffset existingValue,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTimeOffset>(factory, receiver, setter, culture, format: null, ConvertToDateTimeOffsetWithFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTimeOffset> setter,
|
||||
DateTimeOffset existingValue,
|
||||
string format,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTimeOffset>(factory, receiver, setter, culture, format, ConvertToDateTimeOffsetWithFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTimeOffset?> setter,
|
||||
DateTimeOffset? existingValue,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTimeOffset?>(factory, receiver, setter, culture, format: null, ConvertToNullableDateTimeOffsetWithFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="receiver"></param>
|
||||
/// <param name="setter"></param>
|
||||
/// <param name="existingValue"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTimeOffset?> setter,
|
||||
DateTimeOffset? existingValue,
|
||||
string format,
|
||||
CultureInfo culture = null)
|
||||
{
|
||||
return CreateBinderCore<DateTimeOffset?>(factory, receiver, setter, culture, format, ConvertToNullableDateTimeOffsetWithFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -746,6 +894,50 @@ namespace Microsoft.AspNetCore.Components
|
|||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
private static EventCallback<UIChangeEventArgs> CreateBinderCore<T>(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<T> setter,
|
||||
CultureInfo culture,
|
||||
string format,
|
||||
BindConverterWithFormat<T> converter)
|
||||
{
|
||||
Action<UIChangeEventArgs> callback = e =>
|
||||
{
|
||||
T value = default;
|
||||
var converted = false;
|
||||
try
|
||||
{
|
||||
converted = converter(e.Value, culture, format, out value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// We only invoke the setter if the conversion didn't throw, or if the newly-entered value is empty.
|
||||
// If the user entered some non-empty value we couldn't parse, we leave the state of the .NET field
|
||||
// unchanged, which for a two-way binding results in the UI reverting to its previous valid state
|
||||
// because the diff will see the current .NET output no longer matches the render tree since we
|
||||
// patched it to reflect the state of the UI.
|
||||
//
|
||||
// This reversion behavior is valuable because alternatives are problematic:
|
||||
// - If we assigned default(T) on failure, the user would lose whatever data they were editing,
|
||||
// for example if they accidentally pressed an alphabetical key while editing a number with
|
||||
// @bind:event="oninput"
|
||||
// - If the diff mechanism didn't revert to the previous good value, the user wouldn't necessarily
|
||||
// know that the data they are submitting is different from what they think they've typed
|
||||
if (converted)
|
||||
{
|
||||
setter(value);
|
||||
}
|
||||
else if (string.Empty.Equals(e.Value))
|
||||
{
|
||||
setter(default);
|
||||
}
|
||||
};
|
||||
return factory.Create<UIChangeEventArgs>(receiver, callback);
|
||||
}
|
||||
|
||||
// We can't rely on generics + static to cache here unfortunately. That would require us to overload
|
||||
// CreateBinder on T : struct AND T : class, which is not allowed.
|
||||
private static class BinderConverterCache
|
||||
|
|
@ -821,6 +1013,14 @@ namespace Microsoft.AspNetCore.Components
|
|||
{
|
||||
converter = ConvertToNullableDateTime;
|
||||
}
|
||||
else if (typeof(T) == typeof(DateTimeOffset))
|
||||
{
|
||||
converter = ConvertToDateTimeOffset;
|
||||
}
|
||||
else if (typeof(T) == typeof(DateTimeOffset?))
|
||||
{
|
||||
converter = ConvertToNullableDateTimeOffset;
|
||||
}
|
||||
else if (typeof(T).IsEnum)
|
||||
{
|
||||
// We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse.
|
||||
|
|
|
|||
|
|
@ -421,7 +421,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
// For now format is only supported by this specific method.
|
||||
[Fact]
|
||||
public async Task CreateBinder_DateTime_Format()
|
||||
{
|
||||
|
|
@ -442,6 +441,104 @@ namespace Microsoft.AspNetCore.Components
|
|||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableDateTime_Format()
|
||||
{
|
||||
// Arrange
|
||||
var value = (DateTime?)DateTime.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTime?> setter = (_) => value = _;
|
||||
var format = "ddd yyyy-MM-dd";
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_DateTimeOffset()
|
||||
{
|
||||
// Arrange
|
||||
var value = DateTimeOffset.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTimeOffset> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4, 1, 2, 3);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableDateTimeOffset()
|
||||
{
|
||||
// Arrange
|
||||
var value = (DateTimeOffset?)DateTimeOffset.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTimeOffset?> setter = (_) => value = _;
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4, 1, 2, 3);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_DateTimeOffset_Format()
|
||||
{
|
||||
// Arrange
|
||||
var value = DateTimeOffset.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTimeOffset> setter = (_) => value = _;
|
||||
var format = "ddd yyyy-MM-dd";
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBinder_NullableDateTimeOffset_Format()
|
||||
{
|
||||
// Arrange
|
||||
var value = (DateTimeOffset?)DateTimeOffset.Now;
|
||||
var component = new EventCountingComponent();
|
||||
Action<DateTimeOffset?> setter = (_) => value = _;
|
||||
var format = "ddd yyyy-MM-dd";
|
||||
|
||||
var binder = EventCallback.Factory.CreateBinder(component, setter, value, format);
|
||||
|
||||
var expectedValue = new DateTime(2018, 3, 4);
|
||||
|
||||
// Act
|
||||
await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(format), });
|
||||
|
||||
Assert.Equal(expectedValue, value);
|
||||
Assert.Equal(1, component.Count);
|
||||
}
|
||||
|
||||
// This uses a type converter
|
||||
[Fact]
|
||||
public async Task CreateBinder_Guid()
|
||||
|
|
|
|||
|
|
@ -91,6 +91,88 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
|
||||
}
|
||||
|
||||
// The logic is different for verifying culture-invariant fields. The problem is that the logic for what
|
||||
// kinds of text a field accepts is determined by the browser and language - it's not general. So while
|
||||
// type="number" and type="date" produce fixed-format and culture-invariant input/output via the "value"
|
||||
// attribute - the actual input processing is harder to nail down. In practice this is only a problem
|
||||
// with dates.
|
||||
//
|
||||
// For this reason we avoid sending keys directly to the field, and let two-way binding do its thing instead.
|
||||
//
|
||||
// A brief summary:
|
||||
// 1. Input a value (invariant culture if using number field, or current culture to extra input if using date field)
|
||||
// 2. trigger onchange
|
||||
// 3. Verify "value" field (current culture)
|
||||
// 4. Verify the input field's value attribute (invariant culture)
|
||||
//
|
||||
// We need to do step 4 to make sure that the value we're entering can "stick" in the form field.
|
||||
// We can't use ".Text" because DOM reasons :(
|
||||
[Theory]
|
||||
[InlineData("en-US")]
|
||||
[InlineData("fr-FR")]
|
||||
public void CanSetCultureAndParseCultureInvariantNumbersAndDatesWithInputFields(string culture)
|
||||
{
|
||||
var cultureInfo = CultureInfo.GetCultureInfo(culture);
|
||||
|
||||
var selector = new SelectElement(Browser.FindElement(By.Id("culture-selector")));
|
||||
selector.SelectByValue(culture);
|
||||
|
||||
// That should have triggered a redirect, wait for the main test selector to come up.
|
||||
MountTestComponent<GlobalizationBindCases>();
|
||||
WaitUntilExists(By.Id("globalization-cases"));
|
||||
|
||||
var cultureDisplay = WaitUntilExists(By.Id("culture-name-display"));
|
||||
Assert.Equal($"Culture is: {culture}", cultureDisplay.Text);
|
||||
|
||||
// int
|
||||
var input = Browser.FindElement(By.Id("input_type_number_int"));
|
||||
var display = Browser.FindElement(By.Id("input_type_number_int_value"));
|
||||
Browser.Equal(42.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(42.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
input.Clear();
|
||||
input.SendKeys(9000.ToString(CultureInfo.InvariantCulture));
|
||||
input.SendKeys("\t");
|
||||
Browser.Equal(9000.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(9000.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
// decimal
|
||||
input = Browser.FindElement(By.Id("input_type_number_decimal"));
|
||||
display = Browser.FindElement(By.Id("input_type_number_decimal_value"));
|
||||
Browser.Equal(4.2m.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(4.2m.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
input.Clear();
|
||||
input.SendKeys(9000.42m.ToString(CultureInfo.InvariantCulture));
|
||||
input.SendKeys("\t");
|
||||
Browser.Equal(9000.42m.ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(9000.42m.ToString(CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
// datetime
|
||||
input = Browser.FindElement(By.Id("input_type_date_datetime"));
|
||||
display = Browser.FindElement(By.Id("input_type_date_datetime_value"));
|
||||
var extraInput = Browser.FindElement(By.Id("input_type_date_datetime_extrainput"));
|
||||
Browser.Equal(new DateTime(1985, 3, 4).ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(new DateTime(1985, 3, 4).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
ReplaceText(extraInput, new DateTime(2000, 1, 2).ToString(cultureInfo));
|
||||
extraInput.SendKeys("\t");
|
||||
Browser.Equal(new DateTime(2000, 1, 2).ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(new DateTime(2000, 1, 2).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
// datetimeoffset
|
||||
input = Browser.FindElement(By.Id("input_type_date_datetimeoffset"));
|
||||
display = Browser.FindElement(By.Id("input_type_date_datetimeoffset_value"));
|
||||
extraInput = Browser.FindElement(By.Id("input_type_date_datetimeoffset_extrainput"));
|
||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
|
||||
ReplaceText(extraInput, new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo));
|
||||
extraInput.SendKeys("\t");
|
||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
|
||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// see: https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/214
|
||||
//
|
||||
// Calling Clear() can trigger onchange, which will revert the value to its default.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -640,5 +641,241 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.Equal(newValue, () => boundValue.Text);
|
||||
Assert.Equal(newValue, 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 CanBindTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("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")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.SendKeys(Keys.Control + "a"); // select all
|
||||
target.SendKeys("01/02/2000 00:00:00\t");
|
||||
expected = new DateTime(2000, 1, 2);
|
||||
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 CanBindTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("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
|
||||
var expected = new DateTime(2000, 1, 2);
|
||||
target.SendKeys("01/02/2000 00:00:00\t");
|
||||
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 CanBindTextboxDateTimeOffset()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-datetimeoffset"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-datetimeoffset-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("textbox-datetimeoffset-mirror"));
|
||||
var expected = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 01/01/0001 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(expected, () => DateTimeOffset.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.SendKeys(Keys.Control + "a"); // select all
|
||||
target.SendKeys("01/02/2000 00:00:00 +08:00\t");
|
||||
expected = new DateTimeOffset(new DateTime(2000, 1, 2), TimeSpan.FromHours(8));
|
||||
Browser.Equal(expected, () => DateTimeOffset.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTimeOffset.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 CanBindTextboxNullableDateTimeOffset()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-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
|
||||
target.SendKeys("01/02/2000 00:00:00 +08:00" + "\t");
|
||||
var expected = new DateTimeOffset(new DateTime(2000, 1, 2), TimeSpan.FromHours(8));
|
||||
Browser.Equal(expected, () => DateTimeOffset.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTimeOffset.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 CanBindTextboxDateTimeWithFormat()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-datetime-format"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-datetime-format-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("textbox-datetime-format-mirror"));
|
||||
var expected = new DateTime(1985, 3, 4);
|
||||
Assert.Equal("03-04", target.GetAttribute("value"));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to emtpy because that's what we do for `default` when there's a format
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
expected = default;
|
||||
Browser.Equal(string.Empty, () => target.GetAttribute("value"));
|
||||
Assert.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.SendKeys(Keys.Control + "a"); // select all
|
||||
target.SendKeys("01-02\t");
|
||||
expected = new DateTime(DateTime.Now.Year, 1, 2);
|
||||
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 CanBindTextboxNullableDateTimeWithFormat()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-nullable-datetime-format"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetime-format-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetime-format-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
|
||||
target.SendKeys("01-02\t");
|
||||
var expected = new DateTime(DateTime.Now.Year, 1, 2);
|
||||
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 CanBindTextboxDateTimeOffsetWithFormat()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-datetimeoffset-format"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-datetimeoffset-format-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("textbox-datetimeoffset-format-mirror"));
|
||||
var expected = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
|
||||
Assert.Equal("03-04", target.GetAttribute("value"));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to emtpy because that's what we do for `default` when there's a format
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(string.Empty, () => target.GetAttribute("value"));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.SendKeys(Keys.Control + "a"); // select all
|
||||
target.SendKeys("01-02\t");
|
||||
expected = new DateTimeOffset(new DateTime(DateTime.Now.Year, 1, 2), TimeSpan.FromHours(0));
|
||||
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.
|
||||
//
|
||||
// Guess what! Client-side and server-side also understand timezones differently. So for now we're comparing
|
||||
// the parsed output without consideration for the timezone
|
||||
[Fact]
|
||||
public void CanBindTextboxNullableDateTimeOffsetWithFormat()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset"));
|
||||
var boundValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("textbox-nullable-datetimeoffset-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
|
||||
target.SendKeys("01-02" + "\t");
|
||||
var expected = new DateTimeOffset(new DateTime(DateTime.Now.Year, 1, 2), TimeSpan.FromHours(0));
|
||||
Browser.Equal(expected.DateTime, () => DateTimeOffset.Parse(boundValue.Text).DateTime);
|
||||
Assert.Equal(expected.DateTime, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")).DateTime);
|
||||
|
||||
// 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,56 @@
|
|||
<input id="textbox-generic-guid-mirror" @bind="textboxGenericGuidValue" readonly />
|
||||
</p>
|
||||
|
||||
<h2>Date Textboxes (type=text)</h2>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="textbox-datetime" @bind="textboxDateTimeValue" type="text" />
|
||||
<span id="textbox-datetime-value">@textboxDateTimeValue</span>
|
||||
<input id="textbox-datetime-mirror" @bind="textboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="textbox-nullable-datetime" @bind="textboxNullableDateTimeValue" type="text" />
|
||||
<span id="textbox-nullable-datetime-value">@textboxNullableDateTimeValue</span>
|
||||
<input id="textbox-nullable-datetime-mirror" @bind="textboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
DateTimeOffset:
|
||||
<input id="textbox-datetimeoffset" @bind="textboxDateTimeOffsetValue" type="text" />
|
||||
<span id="textbox-datetimeoffset-value">@textboxDateTimeOffsetValue</span>
|
||||
<input id="textbox-datetimeoffset-mirror" @bind="textboxDateTimeOffsetValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTimeOffset:
|
||||
<input id="textbox-nullable-datetimeoffset" @bind="textboxNullableDateTimeOffsetValue" type="text" />
|
||||
<span id="textbox-nullable-datetimeoffset-value">@textboxNullableDateTimeOffsetValue</span>
|
||||
<input id="textbox-nullable-datetimeoffset-mirror" @bind="textboxNullableDateTimeOffsetValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
DateTime (format):
|
||||
<input id="textbox-datetime-format" @bind="textboxDateTimeFormatValue" type="text" @bind:format="MM-dd" />
|
||||
<span id="textbox-datetime-format-value">@textboxDateTimeFormatValue</span>
|
||||
<input id="textbox-datetime-format-mirror" @bind="textboxDateTimeFormatValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime (format):
|
||||
<input id="textbox-nullable-datetime-format" @bind="textboxNullableDateTimeFormatValue" type="text" @bind:format="MM-dd" />
|
||||
<span id="textbox-nullable-datetime-format-value">@textboxNullableDateTimeFormatValue</span>
|
||||
<input id="textbox-nullable-datetime-format-mirror" @bind="textboxNullableDateTimeFormatValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
DateTimeOffset (format):
|
||||
<input id="textbox-datetimeoffset-format" @bind="textboxDateTimeOffsetFormatValue" type="text" @bind:format="MM-dd" />
|
||||
<span id="textbox-datetimeoffset-format-value">@textboxDateTimeOffsetFormatValue</span>
|
||||
<input id="textbox-datetimeoffset-format-mirror" @bind="textboxDateTimeOffsetFormatValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTimeOffset (format):
|
||||
<input id="textbox-nullable-datetimeoffset-format" @bind="textboxNullableDateTimeOffsetFormatValue" type="text" @bind:format="MM-dd" />
|
||||
<span id="textbox-nullable-datetimeoffset-format-value">@textboxNullableDateTimeOffsetFormatValue</span>
|
||||
<input id="textbox-nullable-datetimeoffset-format-mirror" @bind="textboxNullableDateTimeOffsetFormatValue" readonly />
|
||||
</p>
|
||||
|
||||
<h2>Text Area</h2>
|
||||
<p>
|
||||
Initially blank:
|
||||
|
|
@ -145,7 +195,8 @@
|
|||
<h2>Select</h2>
|
||||
<p>
|
||||
<select id="select-box" @bind="selectValue">
|
||||
<optgroup label="Some choices"> <!-- Show it also works with optgroup -->
|
||||
<optgroup label="Some choices">
|
||||
<!-- Show it also works with optgroup -->
|
||||
<option value=@SelectableValue.First>First choice</option>
|
||||
<option value=@SelectableValue.Second>Second choice</option>
|
||||
<option value=@SelectableValue.Third>Third choice</option>
|
||||
|
|
@ -172,40 +223,50 @@
|
|||
</p>
|
||||
|
||||
@code {
|
||||
string textboxInitiallyBlankValue = null;
|
||||
string textboxInitiallyPopulatedValue = "Hello";
|
||||
string textboxInitiallyBlankValue = null;
|
||||
string textboxInitiallyPopulatedValue = "Hello";
|
||||
|
||||
string textAreaInitiallyBlankValue = null;
|
||||
string textAreaInitiallyPopulatedValue = "Hello";
|
||||
string textAreaInitiallyBlankValue = null;
|
||||
string textAreaInitiallyPopulatedValue = "Hello";
|
||||
|
||||
bool? checkboxInitiallyNullValue = null;
|
||||
bool checkboxInitiallyUncheckedValue = false;
|
||||
bool checkboxInitiallyCheckedValue = true;
|
||||
bool? checkboxInitiallyNullValue = null;
|
||||
bool checkboxInitiallyUncheckedValue = false;
|
||||
bool checkboxInitiallyCheckedValue = true;
|
||||
|
||||
int textboxIntValue = -42;
|
||||
int? textboxNullableIntValue = null;
|
||||
long textboxLongValue = 3_000_000_000;
|
||||
long? textboxNullableLongValue = null;
|
||||
float textboxFloatValue = 3.141f;
|
||||
float? textboxNullableFloatValue = null;
|
||||
double textboxDoubleValue = 3.14159265359d;
|
||||
double? textboxNullableDoubleValue = null;
|
||||
decimal textboxDecimalValue = 0.0000000000000000000000000001M;
|
||||
decimal? textboxNullableDecimalValue = null;
|
||||
decimal textboxDecimalInvalidValue = 0.0000000000000000000000000001M;
|
||||
decimal? textboxNullableDecimalInvalidValue = null;
|
||||
int textboxIntValue = -42;
|
||||
int? textboxNullableIntValue = null;
|
||||
long textboxLongValue = 3_000_000_000;
|
||||
long? textboxNullableLongValue = null;
|
||||
float textboxFloatValue = 3.141f;
|
||||
float? textboxNullableFloatValue = null;
|
||||
double textboxDoubleValue = 3.14159265359d;
|
||||
double? textboxNullableDoubleValue = null;
|
||||
decimal textboxDecimalValue = 0.0000000000000000000000000001M;
|
||||
decimal? textboxNullableDecimalValue = null;
|
||||
decimal textboxDecimalInvalidValue = 0.0000000000000000000000000001M;
|
||||
decimal? textboxNullableDecimalInvalidValue = null;
|
||||
|
||||
int textboxGenericIntValue = -42;
|
||||
Guid textboxGenericGuidValue = Guid.Empty;
|
||||
int textboxGenericIntValue = -42;
|
||||
Guid textboxGenericGuidValue = Guid.Empty;
|
||||
|
||||
bool includeFourthOption = false;
|
||||
enum SelectableValue { First, Second, Third, Fourth }
|
||||
SelectableValue selectValue = SelectableValue.Second;
|
||||
SelectableValue selectMarkupValue = SelectableValue.Second;
|
||||
DateTime textboxDateTimeValue = new DateTime(1985, 3, 4);
|
||||
DateTime? textboxNullableDateTimeValue = null;
|
||||
DateTimeOffset textboxDateTimeOffsetValue = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
|
||||
DateTimeOffset? textboxNullableDateTimeOffsetValue = null;
|
||||
|
||||
void AddAndSelectNewSelectOption()
|
||||
{
|
||||
includeFourthOption = true;
|
||||
selectValue = SelectableValue.Fourth;
|
||||
}
|
||||
DateTime textboxDateTimeFormatValue = new DateTime(1985, 3, 4);
|
||||
DateTime? textboxNullableDateTimeFormatValue = null;
|
||||
DateTimeOffset textboxDateTimeOffsetFormatValue = new DateTimeOffset(new DateTime(1985, 3, 4), TimeSpan.FromHours(8));
|
||||
DateTimeOffset? textboxNullableDateTimeOffsetFormatValue = null;
|
||||
|
||||
bool includeFourthOption = false;
|
||||
enum SelectableValue { First, Second, Third, Fourth }
|
||||
SelectableValue selectValue = SelectableValue.Second;
|
||||
SelectableValue selectMarkupValue = SelectableValue.Second;
|
||||
|
||||
void AddAndSelectNewSelectOption()
|
||||
{
|
||||
includeFourthOption = true;
|
||||
selectValue = SelectableValue.Fourth;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
<h3 id="globalization-cases">Globalization Bind Cases</h3>
|
||||
<h3 id="culture-name-display">Culture is: @System.Globalization.CultureInfo.CurrentCulture.Name</h3>
|
||||
|
||||
|
|
@ -25,6 +27,31 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Numbers using bind in number fields</p>
|
||||
<div>
|
||||
int: <input type="number" id="input_type_number_int" value="@inputTypeNumberInt.ToString(CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeNumberInt = value, inputTypeNumberInt, CultureInfo.InvariantCulture)" />
|
||||
<span id="input_type_number_int_value">@inputTypeNumberInt</span>
|
||||
</div>
|
||||
<div>
|
||||
decimal: <input type="number" id="input_type_number_decimal" value="@inputTypeNumberDecimal.ToString(CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeNumberDecimal = value, inputTypeNumberDecimal, CultureInfo.InvariantCulture)"/>
|
||||
<span id="input_type_number_decimal_value">@inputTypeNumberDecimal</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Dates using bind in date fields</p>
|
||||
<div>
|
||||
DateTime: <input type="text" id="input_type_date_datetime_extrainput" @bind="inputTypeDateDateTime" />
|
||||
<input type="date" id="input_type_date_datetime" value="@inputTypeDateDateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeDateDateTime = value, inputTypeDateDateTime, "yyyy-MM-dd", CultureInfo.InvariantCulture)" />
|
||||
<span id="input_type_date_datetime_value">@inputTypeDateDateTime</span>
|
||||
</div>
|
||||
<div>
|
||||
DateTimeOffset: <input type="text" id="input_type_date_datetimeoffset_extrainput" @bind="inputTypeDateDateTimeOffset" />
|
||||
<input type="date" id="input_type_date_datetimeoffset" value="@inputTypeDateDateTimeOffset.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)" @onchange="@EventCallback.Factory.CreateBinder(this, (value) => inputTypeDateDateTimeOffset = value, inputTypeDateDateTimeOffset, "yyyy-MM-dd", CultureInfo.InvariantCulture)"/>
|
||||
<span id="input_type_date_datetimeoffset_value">@inputTypeDateDateTimeOffset</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
int inputTypeTextInt = 42;
|
||||
|
|
@ -32,4 +59,10 @@
|
|||
|
||||
DateTime inputTypeTextDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeTextDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
int inputTypeNumberInt = 42;
|
||||
decimal inputTypeNumberDecimal = 4.2m;
|
||||
|
||||
DateTime inputTypeDateDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeDateDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue