diff --git a/src/Components/Shared/test/TestWebAssemblyJSRuntimeInvoker.cs b/src/Components/Shared/test/TestWebAssemblyJSRuntimeInvoker.cs new file mode 100644 index 000000000000..0f61c93b93ad --- /dev/null +++ b/src/Components/Shared/test/TestWebAssemblyJSRuntimeInvoker.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.AspNetCore.Components.WebAssembly.Services; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + internal class TestWebAssemblyJSRuntimeInvoker : WebAssemblyJSRuntimeInvoker + { + private readonly string _environment; + + public TestWebAssemblyJSRuntimeInvoker(string environment = "Production") + { + _environment = environment; + } + + public override TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) + { + switch (identifier) + { + case "Blazor._internal.getApplicationEnvironment": + return (TResult)(object)_environment; + case "Blazor._internal.getConfig": + return (TResult)(object)null; + default: + throw new NotImplementedException($"{nameof(TestWebAssemblyJSRuntimeInvoker)} has no implementation for '{identifier}'."); + } + } + } +} diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index 783dbb108263..77cd0bac8d91 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -42,6 +42,39 @@ protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNet BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The result of the function invocation. + public TResult InvokeUnmarshalled(string identifier) + => InvokeUnmarshalled(identifier, null, null, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The result of the function invocation. + public TResult InvokeUnmarshalled(string identifier, T0 arg0) + => InvokeUnmarshalled(identifier, arg0, null, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The result of the function invocation. + public TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1) + => InvokeUnmarshalled(identifier, arg0, arg1, null); + /// /// Invokes the JavaScript function registered with the specified identifier. /// @@ -54,7 +87,7 @@ protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNet /// The second argument. /// The third argument. /// The result of the function invocation. - public virtual TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) + public TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) { var result = InternalCalls.InvokeJSUnmarshalled(out var exception, identifier, arg0, arg1, arg2); return exception != null diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs deleted file mode 100644 index 4ba4de1266a5..000000000000 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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; - -namespace Microsoft.JSInterop.WebAssembly -{ - /// - /// Extension methods for . - /// - public static class WebAssemblyJSRuntimeExtensions - { - /// - /// Invokes the JavaScript function registered with the specified identifier. - /// - /// The .NET type corresponding to the function's return value type. - /// The . - /// The identifier used when registering the target function. - /// The result of the function invocation. - public static TResult InvokeUnmarshalled(this WebAssemblyJSRuntime jsRuntime, string identifier) - { - if (jsRuntime is null) - { - throw new ArgumentNullException(nameof(jsRuntime)); - } - - return jsRuntime.InvokeUnmarshalled(identifier, null, null, null); - } - - /// - /// Invokes the JavaScript function registered with the specified identifier. - /// - /// The type of the first argument. - /// The .NET type corresponding to the function's return value type. - /// The . - /// The identifier used when registering the target function. - /// The first argument. - /// The result of the function invocation. - public static TResult InvokeUnmarshalled(this WebAssemblyJSRuntime jsRuntime, string identifier, T0 arg0) - { - if (jsRuntime is null) - { - throw new ArgumentNullException(nameof(jsRuntime)); - } - - return jsRuntime.InvokeUnmarshalled(identifier, arg0, null, null); - } - - /// - /// Invokes the JavaScript function registered with the specified identifier. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The .NET type corresponding to the function's return value type. - /// The . - /// The identifier used when registering the target function. - /// The first argument. - /// The second argument. - /// The result of the function invocation. - public static TResult InvokeUnmarshalled(this WebAssemblyJSRuntime jsRuntime, string identifier, T0 arg0, T1 arg1) - { - if (jsRuntime is null) - { - throw new ArgumentNullException(nameof(jsRuntime)); - } - - return jsRuntime.InvokeUnmarshalled(identifier, arg0, arg1, null); - } - } -} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj b/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj index 1bcc52f0ecc1..5dbb0ebc6807 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs index b23090b1c2de..6e39e38a6761 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -17,7 +18,7 @@ public class WebAssemblyAuthenticationServiceCollectionExtensionsTests [Fact] public void CanResolve_AccessTokenProvider() { - var builder = new WebAssemblyHostBuilder(GetJSRuntime()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddApiAuthorization(); var host = builder.Build(); @@ -27,7 +28,7 @@ public void CanResolve_AccessTokenProvider() [Fact] public void CanResolve_IRemoteAuthenticationService() { - var builder = new WebAssemblyHostBuilder(GetJSRuntime()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddApiAuthorization(); var host = builder.Build(); @@ -37,7 +38,7 @@ public void CanResolve_IRemoteAuthenticationService() [Fact] public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied() { - var builder = new WebAssemblyHostBuilder(GetJSRuntime()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddApiAuthorization(); var host = builder.Build(); @@ -71,7 +72,7 @@ public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied() [Fact] public void ApiAuthorizationOptions_DefaultsCanBeOverriden() { - var builder = new WebAssemblyHostBuilder(GetJSRuntime()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddApiAuthorization(options => { options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions @@ -131,7 +132,7 @@ public void ApiAuthorizationOptions_DefaultsCanBeOverriden() [Fact] public void OidcOptions_ConfigurationDefaultsGetApplied() { - var builder = new WebAssemblyHostBuilder(GetJSRuntime()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.Replace(ServiceDescriptor.Singleton()); builder.Services.AddOidcAuthentication(options => { }); var host = builder.Build(); @@ -169,7 +170,7 @@ public void OidcOptions_ConfigurationDefaultsGetApplied() [Fact] public void OidcOptions_DefaultsCanBeOverriden() { - var builder = new WebAssemblyHostBuilder(GetJSRuntime()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddOidcAuthentication(options => { options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions @@ -244,19 +245,5 @@ public TestNavigationManager() protected override void NavigateToCore(string uri, bool forceLoad) => throw new System.NotImplementedException(); } - - private WebAssemblyJSRuntime GetJSRuntime(string environment = "Production") - { - var jsRuntime = new Mock(); - jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment", null, null, null)) - .Returns(environment) - .Verifiable(); - - jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getConfig", It.IsAny(), null, null)) - .Returns((byte[])null) - .Verifiable(); - - return jsRuntime.Object; - } } } diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index 02017004bf36..7af78d2035eb 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; -using Microsoft.JSInterop.WebAssembly; namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { @@ -35,7 +34,7 @@ public static WebAssemblyHostBuilder CreateDefault(string[] args = default) // We don't use the args for anything right now, but we want to accept them // here so that it shows up this way in the project templates. args ??= Array.Empty(); - var builder = new WebAssemblyHostBuilder(DefaultWebAssemblyJSRuntime.Instance); + var builder = new WebAssemblyHostBuilder(WebAssemblyJSRuntimeInvoker.Instance); // Right now we don't have conventions or behaviors that are specific to this method // however, making this the default for the template allows us to add things like that @@ -47,7 +46,7 @@ public static WebAssemblyHostBuilder CreateDefault(string[] args = default) /// /// Creates an instance of with the minimal configuration. /// - internal WebAssemblyHostBuilder(WebAssemblyJSRuntime jsRuntime) + internal WebAssemblyHostBuilder(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker) { // Private right now because we don't have much reason to expose it. This can be exposed // in the future if we want to give people a choice between CreateDefault and something @@ -58,7 +57,7 @@ internal WebAssemblyHostBuilder(WebAssemblyJSRuntime jsRuntime) InitializeDefaultServices(); - var hostEnvironment = InitializeEnvironment(jsRuntime); + var hostEnvironment = InitializeEnvironment(jsRuntimeInvoker); _createServiceProvider = () => { @@ -66,9 +65,10 @@ internal WebAssemblyHostBuilder(WebAssemblyJSRuntime jsRuntime) }; } - private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntime jsRuntime) + private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker) { - var applicationEnvironment = jsRuntime.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment"); + var applicationEnvironment = jsRuntimeInvoker.InvokeUnmarshalled( + "Blazor._internal.getApplicationEnvironment", null, null, null); var hostEnvironment = new WebAssemblyHostEnvironment(applicationEnvironment); Services.AddSingleton(hostEnvironment); @@ -81,9 +81,8 @@ private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntime js foreach (var configFile in configFiles) { - var appSettingsJson = jsRuntime.InvokeUnmarshalled( - "Blazor._internal.getConfig", - configFile); + var appSettingsJson = jsRuntimeInvoker.InvokeUnmarshalled( + "Blazor._internal.getConfig", configFile, null, null); if (appSettingsJson != null) { diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyJSRuntimeInvoker.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyJSRuntimeInvoker.cs new file mode 100644 index 000000000000..4592c2ce9c4c --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyJSRuntimeInvoker.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.JSInterop.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Services +{ + /// + /// This class exists to enable unit testing for code that needs to call + /// . + /// + /// We should only use this in non-perf-critical code paths (for example, during hosting startup, + /// where we only call this a fixed number of times, and not during rendering where it might be + /// called arbitrarily frequently due to application logic). In perf-critical code paths, use + /// and call it directly. + /// + /// It might not ultimately make any difference but we won't know until we integrate AoT support. + /// When AoT is used, it's possible that virtual dispatch will force fallback on the interpreter. + /// + internal class WebAssemblyJSRuntimeInvoker + { + public static WebAssemblyJSRuntimeInvoker Instance = new WebAssemblyJSRuntimeInvoker(); + + public virtual TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) + => DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled(identifier, arg0, arg1, arg2); + } +} diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs deleted file mode 100644 index 74a251a28e94..000000000000 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 Microsoft.JSInterop.WebAssembly; -using Moq; - -namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting -{ - public class TestWebAssemblyJSRuntime - { - public static WebAssemblyJSRuntime Create(string environment = "Production") - { - var jsRuntime = new Mock(); - jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment", null, null, null)) - .Returns(environment) - .Verifiable(); - - jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getConfig", It.IsAny(), null, null)) - .Returns((byte[])null) - .Verifiable(); - - return jsRuntime.Object; - } - } -} diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs index b6378dca9a2e..b003edafa700 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs @@ -21,7 +21,7 @@ public class WebAssemblyHostBuilderTest public void Build_AllowsConfiguringConfiguration() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Configuration.AddInMemoryCollection(new[] { @@ -39,7 +39,7 @@ public void Build_AllowsConfiguringConfiguration() public void Build_AllowsConfiguringServices() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); // This test also verifies that we create a scope. builder.Services.AddScoped(); @@ -55,7 +55,7 @@ public void Build_AllowsConfiguringServices() public void Build_AllowsConfiguringContainer() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddScoped(); var factory = new MyFakeServiceProviderFactory(); @@ -73,7 +73,7 @@ public void Build_AllowsConfiguringContainer() public void Build_AllowsConfiguringContainer_WithDelegate() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddScoped(); @@ -96,7 +96,7 @@ public void Build_AllowsConfiguringContainer_WithDelegate() public void Build_InDevelopment_ConfiguresWithServiceProviderWithScopeValidation() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create(environment: "Development")); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker(environment: "Development")); builder.Services.AddScoped(); builder.Services.AddSingleton(); @@ -113,7 +113,7 @@ public void Build_InDevelopment_ConfiguresWithServiceProviderWithScopeValidation public void Build_InProduction_ConfiguresWithServiceProviderWithScopeValidation() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddScoped(); builder.Services.AddSingleton(); @@ -165,7 +165,7 @@ public IServiceProvider CreateServiceProvider(MyFakeDIBuilderThing containerBuil public void Build_AddsConfigurationToServices() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Configuration.AddInMemoryCollection(new[] { @@ -200,7 +200,7 @@ private static IReadOnlyList DefaultServiceTypes public void Constructor_AddsDefaultServices() { // Arrange & Act - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); // Assert Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count); diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs index a67363ba5cbc..14a404e68676 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs @@ -18,7 +18,7 @@ public class WebAssemblyHostTest public async Task RunAsync_CanExitBasedOnCancellationToken() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); var host = builder.Build(); var cts = new CancellationTokenSource(); @@ -36,7 +36,7 @@ public async Task RunAsync_CanExitBasedOnCancellationToken() public async Task RunAsync_CallingTwiceCausesException() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); var host = builder.Build(); var cts = new CancellationTokenSource(); @@ -56,7 +56,7 @@ public async Task RunAsync_CallingTwiceCausesException() public async Task DisposeAsync_CanDisposeAfterCallingRunAsync() { // Arrange - var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddSingleton(); var host = builder.Build(); diff --git a/src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj b/src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj index 2f9ac20c7c09..25093715b621 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj +++ b/src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj @@ -9,4 +9,8 @@ + + + +