aspnetcore/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs

192 lines
7.2 KiB
C#

// 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.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.JSInterop.Test
{
public class JSRuntimeBaseTest
{
[Fact]
public void DispatchesAsyncCallsWithDistinctAsyncHandles()
{
// Arrange
var runtime = new TestJSRuntime();
// Act
runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true );
runtime.InvokeAsync<object>("test identifier 2", "some other arg");
// Assert
Assert.Collection(runtime.BeginInvokeCalls,
call =>
{
Assert.Equal("test identifier 1", call.Identifier);
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
},
call =>
{
Assert.Equal("test identifier 2", call.Identifier);
Assert.Equal("[\"some other arg\"]", call.ArgsJson);
Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle);
});
}
[Fact]
public void CanCompleteAsyncCallsAsSuccess()
{
// Arrange
var runtime = new TestJSRuntime();
// Act/Assert: Tasks not initially completed
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
Assert.False(unrelatedTask.IsCompleted);
Assert.False(task.IsCompleted);
// Act/Assert: Task can be completed
runtime.OnEndInvoke(
runtime.BeginInvokeCalls[1].AsyncHandle,
/* succeeded: */ true,
"my result");
Assert.False(unrelatedTask.IsCompleted);
Assert.True(task.IsCompleted);
Assert.Equal("my result", task.Result);
}
[Fact]
public void CanCompleteAsyncCallsAsFailure()
{
// Arrange
var runtime = new TestJSRuntime();
// Act/Assert: Tasks not initially completed
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
Assert.False(unrelatedTask.IsCompleted);
Assert.False(task.IsCompleted);
// Act/Assert: Task can be failed
runtime.OnEndInvoke(
runtime.BeginInvokeCalls[1].AsyncHandle,
/* succeeded: */ false,
"This is a test exception");
Assert.False(unrelatedTask.IsCompleted);
Assert.True(task.IsCompleted);
Assert.IsType<AggregateException>(task.Exception);
Assert.IsType<JSException>(task.Exception.InnerException);
Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message);
}
[Fact]
public void CannotCompleteSameAsyncCallMoreThanOnce()
{
// Arrange
var runtime = new TestJSRuntime();
// Act/Assert
runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle;
runtime.OnEndInvoke(asyncHandle, true, null);
var ex = Assert.Throws<ArgumentException>(() =>
{
// Second "end invoke" will fail
runtime.OnEndInvoke(asyncHandle, true, null);
});
Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message);
}
[Fact]
public void SerializesDotNetObjectWrappersInKnownFormat()
{
// Arrange
var runtime = new TestJSRuntime();
var obj1 = new object();
var obj2 = new object();
var obj3 = new object();
// Act
// Showing we can pass the DotNetObject either as top-level args or nested
var obj1Ref = new DotNetObjectRef(obj1);
var obj1DifferentRef = new DotNetObjectRef(obj1);
runtime.InvokeAsync<object>("test identifier",
obj1Ref,
new Dictionary<string, object>
{
{ "obj2", new DotNetObjectRef(obj2) },
{ "obj3", new DotNetObjectRef(obj3) },
{ "obj1SameRef", obj1Ref },
{ "obj1DifferentRef", obj1DifferentRef },
});
// Assert: Serialized as expected
var call = runtime.BeginInvokeCalls.Single();
Assert.Equal("test identifier", call.Identifier);
Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\",\"obj1SameRef\":\"__dotNetObject:1\",\"obj1DifferentRef\":\"__dotNetObject:4\"}]", call.ArgsJson);
// Assert: Objects were tracked
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1));
Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2));
Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3));
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(4));
}
[Fact]
public void SupportsCustomSerializationForArguments()
{
// Arrange
var runtime = new TestJSRuntime();
// Arrange/Act
runtime.InvokeAsync<object>("test identifier",
new WithCustomArgSerializer());
// Asssert
var call = runtime.BeginInvokeCalls.Single();
Assert.Equal("[{\"key1\":\"value1\",\"key2\":123}]", call.ArgsJson);
}
class TestJSRuntime : JSRuntimeBase
{
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
public class BeginInvokeAsyncArgs
{
public long AsyncHandle { get; set; }
public string Identifier { get; set; }
public string ArgsJson { get; set; }
}
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
{
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
{
AsyncHandle = asyncHandle,
Identifier = identifier,
ArgsJson = argsJson,
});
}
public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException)
=> EndInvokeJS(asyncHandle, succeeded, resultOrException);
}
class WithCustomArgSerializer : ICustomArgSerializer
{
public object ToJsonPrimitive()
{
return new Dictionary<string, object>
{
{ "key1", "value1" },
{ "key2", 123 },
};
}
}
}
}