diff --git a/src/Components/test/E2ETest/Tests/FormsInputDateTest.cs b/src/Components/test/E2ETest/Tests/FormsInputDateTest.cs index bc4e0a66f2f3..c14e843206a5 100644 --- a/src/Components/test/E2ETest/Tests/FormsInputDateTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsInputDateTest.cs @@ -45,22 +45,21 @@ public void InputDateInteractsWithEditContext_NonNullableDateTime() // Validates on edit Browser.Equal("valid", () => renewalDateInput.GetDomAttribute("class")); - renewalDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t"); - renewalDateInput.SendKeys("01/01/2000\t"); + SetDateInputValue(renewalDateInput, "2000-01-01"); Browser.Equal("modified valid", () => renewalDateInput.GetDomAttribute("class")); - // Can become invalid - renewalDateInput.SendKeys("11-11-11111\t"); + // Can become invalid (year is out of range for DateTime) + SetDateInputValue(renewalDateInput, "11111-11-11"); Browser.Equal("modified invalid", () => renewalDateInput.GetDomAttribute("class")); Browser.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor); // Empty is invalid, because it's not nullable - renewalDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t"); + SetDateInputValue(renewalDateInput, ""); Browser.Equal("modified invalid", () => renewalDateInput.GetDomAttribute("class")); Browser.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor); // Can become valid - renewalDateInput.SendKeys("01/01/01\t"); + SetDateInputValue(renewalDateInput, "2001-01-01"); Browser.Equal("modified valid", () => renewalDateInput.GetDomAttribute("class")); Browser.Empty(messagesAccessor); } @@ -74,16 +73,16 @@ public void InputDateInteractsWithEditContext_NullableDateTimeOffset() // Validates on edit Browser.Equal("valid", () => expiryDateInput.GetDomAttribute("class")); - expiryDateInput.SendKeys("01-01-2000\t"); + SetDateInputValue(expiryDateInput, "2000-01-01"); Browser.Equal("modified valid", () => expiryDateInput.GetDomAttribute("class")); - // Can become invalid - expiryDateInput.SendKeys("11-11-11111\t"); + // Can become invalid (year is out of range for DateTimeOffset) + SetDateInputValue(expiryDateInput, "11111-11-11"); Browser.Equal("modified invalid", () => expiryDateInput.GetDomAttribute("class")); Browser.Equal(new[] { "The OptionalExpiryDate field must be a date." }, messagesAccessor); // Empty is valid, because it's nullable - expiryDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t"); + SetDateInputValue(expiryDateInput, ""); Browser.Equal("modified valid", () => expiryDateInput.GetDomAttribute("class")); Browser.Empty(messagesAccessor); } @@ -104,13 +103,13 @@ public void InputDateInteractsWithEditContext_TimeInput() // Validates on edit Browser.Equal("valid", () => departureTimeInput.GetDomAttribute("class")); - departureTimeInput.SendKeys("06:43\t"); + SetDateInputValue(departureTimeInput, "06:43"); Browser.Equal("modified valid", () => departureTimeInput.GetDomAttribute("class")); // Can become invalid - // Stricly speaking the following is equivalent to the empty state, because that's how incomplete input is represented + // Strictly speaking the following is equivalent to the empty state, because that's how incomplete input is represented // We don't know of any way to produce a different (non-empty-equivalent) state using UI gestures, so there's nothing else to test - departureTimeInput.SendKeys($"20{Keys.Backspace}\t"); + SetDateInputValue(departureTimeInput, ""); Browser.Equal("modified invalid", () => departureTimeInput.GetDomAttribute("class")); Browser.Equal(new[] { "The DepartureTime field must be a time." }, messagesAccessor); } @@ -131,13 +130,12 @@ public void InputDateInteractsWithEditContext_TimeInput_Step() // Input works with seconds value of zero and has the expected final value Browser.Equal("valid", () => departureTimeInput.GetDomAttribute("class")); - departureTimeInput.SendKeys("111111"); + SetDateInputValue(departureTimeInput, "11:11:11"); Browser.Equal("modified valid", () => departureTimeInput.GetDomAttribute("class")); Browser.Equal("11:11:11", () => departureTimeInput.GetDomProperty("value")); // Input works with non-zero seconds value - // Move to the beginning of the input and put the new time - departureTimeInput.SendKeys(string.Concat(Enumerable.Repeat(Keys.ArrowLeft, 3)) + "101010"); + SetDateInputValue(departureTimeInput, "10:10:10"); Browser.Equal("modified valid", () => departureTimeInput.GetDomAttribute("class")); Browser.Equal("10:10:10", () => departureTimeInput.GetDomProperty("value")); } @@ -151,22 +149,21 @@ public void InputDateInteractsWithEditContext_MonthInput() // Validates on edit Browser.Equal("valid", () => visitMonthInput.GetDomAttribute("class")); - visitMonthInput.SendKeys($"03{Keys.ArrowRight}2005\t"); + SetDateInputValue(visitMonthInput, "2005-03"); Browser.Equal("modified valid", () => visitMonthInput.GetDomAttribute("class")); // Empty is invalid because it's not nullable - visitMonthInput.Clear(); + SetDateInputValue(visitMonthInput, ""); Browser.Equal("modified invalid", () => visitMonthInput.GetDomAttribute("class")); Browser.Equal(new[] { "The VisitMonth field must be a year and month." }, messagesAccessor); - // Invalid year (11111) - visitMonthInput.SendKeys($"11{Keys.ArrowRight}11111\t"); + // Invalid year (11111, out of range for DateTime) + SetDateInputValue(visitMonthInput, "11111-11"); Browser.Equal("modified invalid", () => visitMonthInput.GetDomAttribute("class")); Browser.Equal(new[] { "The VisitMonth field must be a year and month." }, messagesAccessor); // Can become valid again - visitMonthInput.Clear(); - visitMonthInput.SendKeys($"11{Keys.ArrowRight}1111\t"); + SetDateInputValue(visitMonthInput, "1111-11"); Browser.Equal("modified valid", () => visitMonthInput.GetDomAttribute("class")); Browser.Empty(messagesAccessor); } @@ -187,22 +184,21 @@ public void InputDateInteractsWithEditContext_DateTimeLocalInput() // Validates on edit and has the expected value Browser.Equal("valid", () => appointmentInput.GetDomAttribute("class")); - appointmentInput.SendKeys($"01011970{Keys.ArrowRight}05421"); + SetDateInputValue(appointmentInput, "1970-01-01T05:42"); Browser.Equal("modified valid", () => appointmentInput.GetDomAttribute("class")); // Empty is invalid because it's not nullable - appointmentInput.Clear(); + SetDateInputValue(appointmentInput, ""); Browser.Equal("modified invalid", () => appointmentInput.GetDomAttribute("class")); Browser.Equal(new[] { "The AppointmentDateAndTime field must be a date and time." }, messagesAccessor); - // Invalid year (11111) - appointmentInput.SendKeys($"111111111{Keys.ArrowRight}11111"); + // Invalid year (11111, out of range for DateTime) + SetDateInputValue(appointmentInput, "11111-11-11T11:11"); Browser.Equal("modified invalid", () => appointmentInput.GetDomAttribute("class")); Browser.Equal(new[] { "The AppointmentDateAndTime field must be a date and time." }, messagesAccessor); // Can become valid again - appointmentInput.Clear(); - appointmentInput.SendKeys($"11111111{Keys.ArrowRight}11111"); + SetDateInputValue(appointmentInput, "1111-11-11T11:11"); Browser.Equal("modified valid", () => appointmentInput.GetDomAttribute("class")); Browser.Empty(messagesAccessor); } @@ -223,13 +219,12 @@ public void InputDateInteractsWithEditContext_DateTimeLocalInput_Step() // Input works with seconds value of zero (as in, starting from a zero value, which is the default) and has the expected final value Browser.Equal("valid", () => appointmentInput.GetDomAttribute("class")); - appointmentInput.SendKeys($"11111970{Keys.ArrowRight}114216"); + SetDateInputValue(appointmentInput, "1970-11-11T11:42:16"); Browser.Equal("modified valid", () => appointmentInput.GetDomAttribute("class")); Browser.Equal("1970-11-11T11:42:16", () => appointmentInput.GetDomProperty("value")); // Input works when starting with a non-zero seconds value - // Move to the beginning of the input and put the new value - appointmentInput.SendKeys(string.Concat(Enumerable.Repeat(Keys.ArrowLeft, 6)) + $"10101970{Keys.ArrowRight}105321"); + SetDateInputValue(appointmentInput, "1970-10-10T10:53:21"); Browser.Equal("modified valid", () => appointmentInput.GetDomAttribute("class")); Browser.Equal("1970-10-10T10:53:21", () => appointmentInput.GetDomProperty("value")); } @@ -242,4 +237,15 @@ private Func CreateValidationMessagesAccessor(IWebElement appElement) .OrderBy(x => x) .ToArray(); } + + // Sets the value of a native date/time input directly and raises the "change" event that + // InputDate binds to. This avoids the well-known flakiness of driving the browser's native + // date-picker segments via simulated keystrokes (see the class-level comment above), while + // still exercising the real value-parsing and EditContext validation path. + private void SetDateInputValue(IWebElement input, string value) + => ((IJavaScriptExecutor)Browser).ExecuteScript( + "arguments[0].value = arguments[1];" + + "arguments[0].dispatchEvent(new Event('change', { bubbles: true }));", + input, + value); } diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index 43708d63067a..1ab9430e080f 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -12,6 +12,8 @@ true + true + true $(InterceptorsNamespaces);Microsoft.Extensions.Validation.Generated