// Copyright (c) .NET Foundation. All rights reserved. // 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; using Microsoft.AspNetCore.E2ETesting; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { // For now this is limited to server-side execution because we don't have the ability to set the // culture in client-side Blazor. public class GlobalizationTest : BasicTestAppTestBase { public GlobalizationTest( BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture.WithServerExecution(), output) { } protected override void InitializeAsyncCore() { // On WebAssembly, page reloads are expensive so skip if possible Navigate(ServerPathBase, _serverFixture.ExecutionMode == ExecutionMode.Client); MountTestComponent(); WaitUntilExists(By.Id("culture-selector")); } [Theory] [InlineData("en-US")] [InlineData("fr-FR")] public void CanSetCultureAndParseCultueSensitiveNumbersAndDates(string culture) { var cultureInfo = CultureInfo.GetCultureInfo(culture); SetCulture(culture); // int var input = Browser.FindElement(By.Id("input_type_text_int")); var display = Browser.FindElement(By.Id("input_type_text_int_value")); Browser.Equal(42.ToString(cultureInfo), () => display.Text); input.Clear(); input.SendKeys(9000.ToString("0,000", cultureInfo)); input.SendKeys("\t"); Browser.Equal(9000.ToString(cultureInfo), () => display.Text); // decimal input = Browser.FindElement(By.Id("input_type_text_decimal")); display = Browser.FindElement(By.Id("input_type_text_decimal_value")); Browser.Equal(4.2m.ToString(cultureInfo), () => display.Text); input.Clear(); input.SendKeys(9000.42m.ToString("0,000.00", cultureInfo)); input.SendKeys("\t"); Browser.Equal(9000.42m.ToString(cultureInfo), () => display.Text); // datetime input = Browser.FindElement(By.Id("input_type_text_datetime")); display = Browser.FindElement(By.Id("input_type_text_datetime_value")); Browser.Equal(new DateTime(1985, 3, 4).ToString(cultureInfo), () => display.Text); ReplaceText(input, new DateTime(2000, 1, 2).ToString(cultureInfo)); input.SendKeys("\t"); Browser.Equal(new DateTime(2000, 1, 2).ToString(cultureInfo), () => display.Text); // datetimeoffset input = Browser.FindElement(By.Id("input_type_text_datetimeoffset")); display = Browser.FindElement(By.Id("input_type_text_datetimeoffset_value")); Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString(cultureInfo), () => display.Text); ReplaceText(input, new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo)); input.SendKeys("\t"); 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); SetCulture(culture); // 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")); } [Theory] [InlineData("en-US")] [InlineData("fr-FR")] public void CanSetCultureAndParseCultureInvariantNumbersAndDatesWithFormComponents(string culture) { var cultureInfo = CultureInfo.GetCultureInfo(culture); SetCulture(culture); // int var input = Browser.FindElement(By.Id("inputnumber_int")); var display = Browser.FindElement(By.Id("inputnumber_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("inputnumber_decimal")); display = Browser.FindElement(By.Id("inputnumber_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("inputdate_datetime")); display = Browser.FindElement(By.Id("inputdate_datetime_value")); var extraInput = Browser.FindElement(By.Id("inputdate_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("inputdate_datetimeoffset")); display = Browser.FindElement(By.Id("inputdate_datetimeoffset_value")); extraInput = Browser.FindElement(By.Id("inputdate_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. private static void ReplaceText(IWebElement element, string text) { element.SendKeys(Keys.Control + "a"); element.SendKeys(text); } private void SetCulture(string culture) { var selector = new SelectElement(Browser.FindElement(By.Id("culture-selector"))); selector.SelectByValue(culture); // Click the link to return back to the test page WaitUntilExists(By.ClassName("return-from-culture-setter")).Click(); // That should have triggered a page load, so wait for the main test selector to come up. MountTestComponent(); WaitUntilExists(By.Id("globalization-cases")); var cultureDisplay = WaitUntilExists(By.Id("culture-name-display")); Assert.Equal($"Culture is: {culture}", cultureDisplay.Text); } } }