From 6d1ef39dcc72d70db43473510376cdeb2fa0a77a Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 5 Jul 2019 17:27:56 -0700 Subject: [PATCH] Add server-side localization test. --- .../ServerExecutionTests/LocalizationTest.cs | 53 ++++++++ .../BasicTestApp/BasicTestApp.csproj | 13 ++ .../BasicTestApp/CulturePicker.razor | 18 +++ .../test/testassets/BasicTestApp/Index.razor | 16 ++- .../BasicTestApp/LocalizedText.razor | 2 + .../BasicTestApp/Resources.Designer.cs | 72 ++++++++++ .../testassets/BasicTestApp/Resources.fr.resx | 123 ++++++++++++++++++ .../testassets/BasicTestApp/Resources.resx | 123 ++++++++++++++++++ .../Controllers/CultureController.cs | 21 +++ .../test/testassets/TestServer/Startup.cs | 36 +++-- 10 files changed, 461 insertions(+), 16 deletions(-) create mode 100644 src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs create mode 100644 src/Components/test/testassets/BasicTestApp/CulturePicker.razor create mode 100644 src/Components/test/testassets/BasicTestApp/LocalizedText.razor create mode 100644 src/Components/test/testassets/BasicTestApp/Resources.Designer.cs create mode 100644 src/Components/test/testassets/BasicTestApp/Resources.fr.resx create mode 100644 src/Components/test/testassets/BasicTestApp/Resources.resx create mode 100644 src/Components/test/testassets/TestServer/Controllers/CultureController.cs diff --git a/src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs new file mode 100644 index 0000000000..94faed00bc --- /dev/null +++ b/src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs @@ -0,0 +1,53 @@ +// 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 BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +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 LocalizationTest : BasicTestAppTestBase + { + public LocalizationTest( + 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", "Hello!")] + [InlineData("fr-FR", "Bonjour!")] + public void CanSetCultureAndReadLocalizedResources(string culture, string message) + { + 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(); + + var cultureDisplay = WaitUntilExists(By.Id("culture-name-display")); + Assert.Equal($"Culture is: {culture}", cultureDisplay.Text); + + var messageDisplay = Browser.FindElement(By.Id("message-display")); + Assert.Equal(message, messageDisplay.Text); + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index 9b63d280c8..6cd50a2b8d 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -19,4 +19,17 @@ + + + True + True + Resources.resx + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/src/Components/test/testassets/BasicTestApp/CulturePicker.razor b/src/Components/test/testassets/BasicTestApp/CulturePicker.razor new file mode 100644 index 0000000000..aacf90bb12 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/CulturePicker.razor @@ -0,0 +1,18 @@ +@inject IUriHelper UriHelper + +

Select your language

+ + +@code { + void OnSelected(UIChangeEventArgs e) + { + // Included fragment to preserve choice of Blazor client or server. + var redirect = new Uri(UriHelper.GetAbsoluteUri()).GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.UriEscaped); + var query = $"?culture={Uri.EscapeDataString((string)e.Value)}&redirectUri={redirect}"; + UriHelper.NavigateTo("/Culture/SetCulture" + query, forceLoad: true); + } +} diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 9aa1cdfa04..d48449cac6 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -1,6 +1,6 @@ @using Microsoft.AspNetCore.Components.RenderTree
- Select test: + Select test: - @if (SelectedComponentType != null) - { - @(SelectedComponentType.Name.Replace(".", "/")).cshtml - } -
+ @System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription + + @if (SelectedComponentType != null) + { + @(SelectedComponentType.Name.Replace(".", "/")).razor + } +
diff --git a/src/Components/test/testassets/BasicTestApp/LocalizedText.razor b/src/Components/test/testassets/BasicTestApp/LocalizedText.razor new file mode 100644 index 0000000000..8423f226dd --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/LocalizedText.razor @@ -0,0 +1,2 @@ +

Culture is: @System.Globalization.CultureInfo.CurrentCulture.Name

+

@Resources.Message

diff --git a/src/Components/test/testassets/BasicTestApp/Resources.Designer.cs b/src/Components/test/testassets/BasicTestApp/Resources.Designer.cs new file mode 100644 index 0000000000..e747f3beff --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BasicTestApp { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BasicTestApp.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Hello!. + /// + internal static string Message { + get { + return ResourceManager.GetString("Message", resourceCulture); + } + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/Resources.fr.resx b/src/Components/test/testassets/BasicTestApp/Resources.fr.resx new file mode 100644 index 0000000000..1371dedf99 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/Resources.fr.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bonjour! + + \ No newline at end of file diff --git a/src/Components/test/testassets/BasicTestApp/Resources.resx b/src/Components/test/testassets/BasicTestApp/Resources.resx new file mode 100644 index 0000000000..f160f44968 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Hello! + + \ No newline at end of file diff --git a/src/Components/test/testassets/TestServer/Controllers/CultureController.cs b/src/Components/test/testassets/TestServer/Controllers/CultureController.cs new file mode 100644 index 0000000000..f3cbaf8286 --- /dev/null +++ b/src/Components/test/testassets/TestServer/Controllers/CultureController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc; + +namespace Components.TestServer.Controllers +{ + [Route("[controller]/[action]")] + public class CultureController : Controller + { + public IActionResult SetCulture(string culture, string redirectUri) + { + if (culture != null) + { + HttpContext.Response.Cookies.Append( + CookieRequestCultureProvider.DefaultCookieName, + CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture))); + } + + return LocalRedirect(redirectUri); + } + } +} diff --git a/src/Components/test/testassets/TestServer/Startup.cs b/src/Components/test/testassets/TestServer/Startup.cs index b0a7269e8f..cecc31e7f5 100644 --- a/src/Components/test/testassets/TestServer/Startup.cs +++ b/src/Components/test/testassets/TestServer/Startup.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using BasicTestApp; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; @@ -63,14 +64,22 @@ namespace TestServer app.UseAuthentication(); // Mount the server-side Blazor app on /subdir - app.Map("/subdir", subdirApp => + app.Map("/subdir", app => { - subdirApp.UseStaticFiles(); - subdirApp.UseClientSideBlazorFiles(); + app.UseStaticFiles(); + app.UseClientSideBlazorFiles(); - subdirApp.UseRouting(); + app.UseRequestLocalization(options => + { + options.AddSupportedCultures("en-US", "fr-FR"); + options.AddSupportedUICultures("en-US", "fr-FR"); - subdirApp.UseEndpoints(endpoints => + // Cookie culture provider is included by default. + }); + + app.UseRouting(); + + app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(typeof(Index), selector: "root"); endpoints.MapFallbackToClientSideBlazor("index.html"); @@ -78,12 +87,12 @@ namespace TestServer }); // Separately, mount a prerendered server-side Blazor app on /prerendered - app.Map("/prerendered", subdirApp => + app.Map("/prerendered", app => { - subdirApp.UsePathBase("/prerendered"); - subdirApp.UseStaticFiles(); - subdirApp.UseRouting(); - subdirApp.UseEndpoints(endpoints => + app.UsePathBase("/prerendered"); + app.UseStaticFiles(); + app.UseRouting(); + app.UseEndpoints(endpoints => { endpoints.MapFallbackToPage("/PrerenderedHost"); endpoints.MapBlazorHub(); @@ -96,6 +105,13 @@ namespace TestServer { endpoints.MapControllers(); endpoints.MapRazorPages(); + + // Redirect for convenience when testing locally since we're hosting the app at /subdir/ + endpoints.Map("/", context => + { + context.Response.Redirect("/subdir"); + return Task.CompletedTask; + }); }); } }