|
|
@@ -2,6 +2,7 @@
|
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
|
|
using System;
|
|
|
+using System.Collections.Concurrent;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Threading.Tasks;
|
|
|
@@ -161,6 +162,251 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ [Fact]
|
|
|
+ public async Task CanRenderAsyncTopLevelComponents()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var renderer = new TestRenderer();
|
|
|
+ var component = new AsyncComponent(5); // Triggers n renders, the first one creating <p>n</p> and the n-1 renders asynchronously update the value.
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var componentId = renderer.AssignRootComponentId(component);
|
|
|
+ await renderer.RenderRootComponentAsync(componentId);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ Assert.Equal(5, renderer.Batches.Count);
|
|
|
+
|
|
|
+ // First render
|
|
|
+ var create = renderer.Batches[0];
|
|
|
+ var diff = create.DiffsByComponentId[componentId].Single();
|
|
|
+ Assert.Collection(diff.Edits,
|
|
|
+ edit =>
|
|
|
+ {
|
|
|
+ Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type);
|
|
|
+ Assert.Equal(0, edit.ReferenceFrameIndex);
|
|
|
+ });
|
|
|
+ AssertFrame.Element(create.ReferenceFrames[0], "p", 2);
|
|
|
+ AssertFrame.Text(create.ReferenceFrames[1], "5");
|
|
|
+
|
|
|
+ // Second render
|
|
|
+ for (int i = 1; i < 5; i++)
|
|
|
+ {
|
|
|
+
|
|
|
+ var update = renderer.Batches[i];
|
|
|
+ var updateDiff = update.DiffsByComponentId[componentId].Single();
|
|
|
+ Assert.Collection(updateDiff.Edits,
|
|
|
+ edit =>
|
|
|
+ {
|
|
|
+ Assert.Equal(RenderTreeEditType.StepIn, edit.Type);
|
|
|
+ },
|
|
|
+ edit =>
|
|
|
+ {
|
|
|
+ Assert.Equal(RenderTreeEditType.UpdateText, edit.Type);
|
|
|
+ },
|
|
|
+ edit =>
|
|
|
+ {
|
|
|
+ Assert.Equal(RenderTreeEditType.StepOut, edit.Type);
|
|
|
+ });
|
|
|
+ AssertFrame.Text(update.ReferenceFrames[0], (5 - i).ToString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task CanRenderAsyncNestedComponents()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var renderer = new TestRenderer();
|
|
|
+ var component = new NestedAsyncComponent();
|
|
|
+
|
|
|
+ // Act/Assert
|
|
|
+ var componentId = renderer.AssignRootComponentId(component);
|
|
|
+ var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
|
|
|
+ await renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
|
|
+ {
|
|
|
+ [nameof(NestedAsyncComponent.EventActions)] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
|
|
+ {
|
|
|
+ [0] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async: true),
|
|
|
+ },
|
|
|
+ [1] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async: true),
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.WhatToRender)] = new Dictionary<int, Func<NestedAsyncComponent, RenderFragment>>
|
|
|
+ {
|
|
|
+ [0] = CreateRenderFactory(new[] { 1 }),
|
|
|
+ [1] = CreateRenderFactory(Array.Empty<int>())
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.Log)] = log
|
|
|
+ }));
|
|
|
+
|
|
|
+ var logForParent = log.Where(l => l.id == 0).ToArray();
|
|
|
+ var logForChild = log.Where(l => l.id == 1).ToArray();
|
|
|
+
|
|
|
+ AssertStream(0, logForParent);
|
|
|
+ AssertStream(1, logForChild);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task CanRenderAsyncComponentsWithSyncChildComponents()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var renderer = new TestRenderer();
|
|
|
+ var component = new NestedAsyncComponent();
|
|
|
+
|
|
|
+ // Act/Assert
|
|
|
+ var componentId = renderer.AssignRootComponentId(component);
|
|
|
+ var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
|
|
|
+ await renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
|
|
+ {
|
|
|
+ [nameof(NestedAsyncComponent.EventActions)] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
|
|
+ {
|
|
|
+ [0] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async: true),
|
|
|
+ },
|
|
|
+ [1] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInitAsyncAsync),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync),
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.WhatToRender)] = new Dictionary<int, Func<NestedAsyncComponent, RenderFragment>>
|
|
|
+ {
|
|
|
+ [0] = CreateRenderFactory(new[] { 1 }),
|
|
|
+ [1] = CreateRenderFactory(Array.Empty<int>())
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.Log)] = log
|
|
|
+ }));
|
|
|
+
|
|
|
+ var logForParent = log.Where(l => l.id == 0).ToArray();
|
|
|
+ var logForChild = log.Where(l => l.id == 1).ToArray();
|
|
|
+
|
|
|
+ AssertStream(0, logForParent);
|
|
|
+ AssertStream(1, logForChild);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task CanRenderAsyncComponentsWithAsyncChildInit()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var renderer = new TestRenderer();
|
|
|
+ var component = new NestedAsyncComponent();
|
|
|
+
|
|
|
+ // Act/Assert
|
|
|
+ var componentId = renderer.AssignRootComponentId(component);
|
|
|
+ var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
|
|
|
+ await renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
|
|
+ {
|
|
|
+ [nameof(NestedAsyncComponent.EventActions)] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
|
|
+ {
|
|
|
+ [0] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async: true),
|
|
|
+ },
|
|
|
+ [1] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync),
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.WhatToRender)] = new Dictionary<int, Func<NestedAsyncComponent, RenderFragment>>
|
|
|
+ {
|
|
|
+ [0] = CreateRenderFactory(new[] { 1 }),
|
|
|
+ [1] = CreateRenderFactory(Array.Empty<int>())
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.Log)] = log
|
|
|
+ }));
|
|
|
+
|
|
|
+ var logForParent = log.Where(l => l.id == 0).ToArray();
|
|
|
+ var logForChild = log.Where(l => l.id == 1).ToArray();
|
|
|
+
|
|
|
+ AssertStream(0, logForParent);
|
|
|
+ AssertStream(1, logForChild);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task CanRenderAsyncComponentsWithMultipleAsyncChildren()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var renderer = new TestRenderer();
|
|
|
+ var component = new NestedAsyncComponent();
|
|
|
+
|
|
|
+ // Act/Assert
|
|
|
+ var componentId = renderer.AssignRootComponentId(component);
|
|
|
+ var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>();
|
|
|
+ await renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
|
|
+ {
|
|
|
+ [nameof(NestedAsyncComponent.EventActions)] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
|
|
+ {
|
|
|
+ [0] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(0, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async: true),
|
|
|
+ },
|
|
|
+ [1] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(1, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async:true),
|
|
|
+ },
|
|
|
+ [2] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(2, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(2, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(2, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(2, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async:true),
|
|
|
+ },
|
|
|
+ [3] = new List<NestedAsyncComponent.ExecutionAction>
|
|
|
+ {
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(3, NestedAsyncComponent.EventType.OnInit),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(3, NestedAsyncComponent.EventType.OnInitAsyncAsync, async:true),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(3, NestedAsyncComponent.EventType.OnParametersSet),
|
|
|
+ NestedAsyncComponent.ExecutionAction.On(3, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync, async:true),
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.WhatToRender)] = new Dictionary<int, Func<NestedAsyncComponent, RenderFragment>>
|
|
|
+ {
|
|
|
+ [0] = CreateRenderFactory(new[] { 1, 2 }),
|
|
|
+ [1] = CreateRenderFactory(new[] { 3 }),
|
|
|
+ [2] = CreateRenderFactory(Array.Empty<int>()),
|
|
|
+ [3] = CreateRenderFactory(Array.Empty<int>())
|
|
|
+ },
|
|
|
+ [nameof(NestedAsyncComponent.Log)] = log
|
|
|
+ }));
|
|
|
+
|
|
|
+ var logForParent = log.Where(l => l.id == 0).ToArray();
|
|
|
+ var logForFirstChild = log.Where(l => l.id == 1).ToArray();
|
|
|
+ var logForSecondChild = log.Where(l => l.id == 2).ToArray();
|
|
|
+ var logForThirdChild = log.Where(l => l.id == 3).ToArray();
|
|
|
+
|
|
|
+ AssertStream(0, logForParent);
|
|
|
+ AssertStream(1, logForFirstChild);
|
|
|
+ AssertStream(2, logForSecondChild);
|
|
|
+ AssertStream(3, logForThirdChild);
|
|
|
+ }
|
|
|
+
|
|
|
[Fact]
|
|
|
public void CanDispatchEventsToTopLevelComponents()
|
|
|
{
|
|
|
@@ -1233,13 +1479,16 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
_renderFragment = renderFragment;
|
|
|
}
|
|
|
|
|
|
- public void Init(RenderHandle renderHandle)
|
|
|
+ public void Configure(RenderHandle renderHandle)
|
|
|
{
|
|
|
_renderHandle = renderHandle;
|
|
|
}
|
|
|
|
|
|
- public void SetParameters(ParameterCollection parameters)
|
|
|
- => TriggerRender();
|
|
|
+ public Task SetParametersAsync(ParameterCollection parameters)
|
|
|
+ {
|
|
|
+ TriggerRender();
|
|
|
+ return Task.CompletedTask;
|
|
|
+ }
|
|
|
|
|
|
public void TriggerRender()
|
|
|
=> _renderHandle.Render(_renderFragment);
|
|
|
@@ -1273,11 +1522,14 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
|
|
|
public RenderHandle RenderHandle { get; private set; }
|
|
|
|
|
|
- public void Init(RenderHandle renderHandle)
|
|
|
+ public void Configure(RenderHandle renderHandle)
|
|
|
=> RenderHandle = renderHandle;
|
|
|
|
|
|
- public void SetParameters(ParameterCollection parameters)
|
|
|
- => parameters.SetParameterProperties(this);
|
|
|
+ public Task SetParametersAsync(ParameterCollection parameters)
|
|
|
+ {
|
|
|
+ parameters.SetParameterProperties(this);
|
|
|
+ return Task.CompletedTask;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private class EventComponent : AutoRenderComponent, IComponent, IHandleEvent
|
|
|
@@ -1337,7 +1589,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
|
|
{
|
|
|
builder.AddContent(0, "Parent here");
|
|
|
-
|
|
|
+
|
|
|
if (IncludeChild)
|
|
|
{
|
|
|
builder.OpenComponent<T>(1);
|
|
|
@@ -1353,7 +1605,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private class ReRendersParentComponent : AutoRenderComponent
|
|
|
{
|
|
|
[Parameter]
|
|
|
@@ -1380,13 +1632,14 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
|
|
|
private RenderHandle _renderHandle;
|
|
|
|
|
|
- public void Init(RenderHandle renderHandle)
|
|
|
+ public void Configure(RenderHandle renderHandle)
|
|
|
=> _renderHandle = renderHandle;
|
|
|
|
|
|
- public void SetParameters(ParameterCollection parameters)
|
|
|
+ public Task SetParametersAsync(ParameterCollection parameters)
|
|
|
{
|
|
|
parameters.SetParameterProperties(this);
|
|
|
Render();
|
|
|
+ return Task.CompletedTask;
|
|
|
}
|
|
|
|
|
|
public void HandleEvent(EventHandlerInvoker binding, UIEventArgs args)
|
|
|
@@ -1409,11 +1662,12 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
private readonly List<RenderHandle> _renderHandles
|
|
|
= new List<RenderHandle>();
|
|
|
|
|
|
- public void Init(RenderHandle renderHandle)
|
|
|
+ public void Configure(RenderHandle renderHandle)
|
|
|
=> _renderHandles.Add(renderHandle);
|
|
|
|
|
|
- public void SetParameters(ParameterCollection parameters)
|
|
|
+ public Task SetParametersAsync(ParameterCollection parameters)
|
|
|
{
|
|
|
+ return Task.CompletedTask;
|
|
|
}
|
|
|
|
|
|
public void TriggerRender()
|
|
|
@@ -1463,9 +1717,10 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
OnAfterRenderCallCount++;
|
|
|
}
|
|
|
|
|
|
- void IComponent.SetParameters(ParameterCollection parameters)
|
|
|
+ Task IComponent.SetParametersAsync(ParameterCollection parameters)
|
|
|
{
|
|
|
TriggerRender();
|
|
|
+ return Task.CompletedTask;
|
|
|
}
|
|
|
|
|
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
|
|
@@ -1501,5 +1756,229 @@ namespace Microsoft.AspNetCore.Components.Test
|
|
|
return NextUpdateDisplayReturnTask;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private class AsyncComponent : IComponent
|
|
|
+ {
|
|
|
+ private RenderHandle _renderHandler;
|
|
|
+
|
|
|
+ public AsyncComponent(int number)
|
|
|
+ {
|
|
|
+ Number = number;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Number { get; set; }
|
|
|
+
|
|
|
+ public void Configure(RenderHandle renderHandle)
|
|
|
+ {
|
|
|
+ _renderHandler = renderHandle;
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task SetParametersAsync(ParameterCollection parameters)
|
|
|
+ {
|
|
|
+ int n;
|
|
|
+ while (Number > 0)
|
|
|
+ {
|
|
|
+ n = Number;
|
|
|
+ _renderHandler.Render(CreateFragment);
|
|
|
+ Number--;
|
|
|
+ await Task.Yield();
|
|
|
+ };
|
|
|
+
|
|
|
+ // Cheap closure
|
|
|
+ void CreateFragment(RenderTreeBuilder builder)
|
|
|
+ {
|
|
|
+ var s = 0;
|
|
|
+ builder.OpenElement(s++, "p");
|
|
|
+ builder.AddContent(s++, n);
|
|
|
+ builder.CloseElement();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AssertStream(int expectedId, (int id, NestedAsyncComponent.EventType @event)[] logStream)
|
|
|
+ {
|
|
|
+ // OnInit runs first
|
|
|
+ Assert.Equal((expectedId, NestedAsyncComponent.EventType.OnInit), logStream[0]);
|
|
|
+
|
|
|
+ // OnInit async completes
|
|
|
+ Assert.Single(logStream.Skip(1),
|
|
|
+ e => e == (expectedId, NestedAsyncComponent.EventType.OnInitAsyncAsync) || e == (expectedId, NestedAsyncComponent.EventType.OnInitAsyncSync));
|
|
|
+
|
|
|
+ var parametersSetEvent = logStream.Where(le => le == (expectedId, NestedAsyncComponent.EventType.OnParametersSet)).ToArray();
|
|
|
+ // OnParametersSet gets called at least once
|
|
|
+ Assert.NotEmpty(parametersSetEvent);
|
|
|
+
|
|
|
+ var parametersSetAsyncEvent = logStream
|
|
|
+ .Where(le => le == (expectedId, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync) ||
|
|
|
+ le == (expectedId, NestedAsyncComponent.EventType.OnParametersSetAsyncSync))
|
|
|
+ .ToArray();
|
|
|
+ // OnParametersSetAsync async gets called at least once
|
|
|
+ Assert.NotEmpty(parametersSetAsyncEvent);
|
|
|
+
|
|
|
+ // The same number of OnParametersSet and OnParametersSetAsync get produced
|
|
|
+ Assert.Equal(parametersSetEvent.Length, parametersSetAsyncEvent.Length);
|
|
|
+
|
|
|
+ // The log ends with an OnParametersSetAsync event
|
|
|
+ Assert.True(logStream.Last() == (expectedId, NestedAsyncComponent.EventType.OnParametersSetAsyncSync) ||
|
|
|
+ logStream.Last() == (expectedId, NestedAsyncComponent.EventType.OnParametersSetAsyncAsync));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Func<NestedAsyncComponent, RenderFragment> CreateRenderFactory(int[] childrenToRender)
|
|
|
+ {
|
|
|
+ // For some reason nameof doesn't work inside a nested lambda, so capturing the value here.
|
|
|
+ var eventActionsName = nameof(NestedAsyncComponent.EventActions);
|
|
|
+ var whatToRenderName = nameof(NestedAsyncComponent.WhatToRender);
|
|
|
+ var testIdName = nameof(NestedAsyncComponent.TestId);
|
|
|
+ var logName = nameof(NestedAsyncComponent.Log);
|
|
|
+
|
|
|
+ return component => builder =>
|
|
|
+ {
|
|
|
+ int s = 0;
|
|
|
+ builder.OpenElement(s++, "div");
|
|
|
+ builder.AddContent(s++, $"Id: {component.TestId} BuildRenderTree, {Guid.NewGuid()}");
|
|
|
+ foreach (var child in childrenToRender)
|
|
|
+ {
|
|
|
+ builder.OpenComponent<NestedAsyncComponent>(s++);
|
|
|
+ builder.AddAttribute(s++, eventActionsName, component.EventActions);
|
|
|
+ builder.AddAttribute(s++, whatToRenderName, component.WhatToRender);
|
|
|
+ builder.AddAttribute(s++, testIdName, child);
|
|
|
+ builder.AddAttribute(s++, logName, component.Log);
|
|
|
+ builder.CloseComponent();
|
|
|
+ }
|
|
|
+
|
|
|
+ builder.CloseElement();
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private class NestedAsyncComponent : ComponentBase
|
|
|
+ {
|
|
|
+ private RenderHandle _renderHandle;
|
|
|
+
|
|
|
+ public void Configure(RenderHandle renderHandle)
|
|
|
+ {
|
|
|
+ _renderHandle = renderHandle;
|
|
|
+ }
|
|
|
+
|
|
|
+ [Parameter] public IDictionary<int, IList<ExecutionAction>> EventActions { get; set; }
|
|
|
+
|
|
|
+ [Parameter] public IDictionary<int, Func<NestedAsyncComponent, RenderFragment>> WhatToRender { get; set; }
|
|
|
+
|
|
|
+ [Parameter] public int TestId { get; set; }
|
|
|
+
|
|
|
+ [Parameter] public ConcurrentQueue<(int testId, EventType @event)> Log { get; set; }
|
|
|
+
|
|
|
+ protected override void OnInit()
|
|
|
+ {
|
|
|
+ if (TryGetEntry(EventType.OnInit, out var entry))
|
|
|
+ {
|
|
|
+ var result = entry.EventAction();
|
|
|
+ LogResult(result.Result);
|
|
|
+ }
|
|
|
+ base.OnInit();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override async Task OnInitAsync()
|
|
|
+ {
|
|
|
+ if (TryGetEntry(EventType.OnInitAsyncSync, out var entrySync))
|
|
|
+ {
|
|
|
+ var result = await entrySync.EventAction();
|
|
|
+ LogResult(result);
|
|
|
+ }
|
|
|
+ else if (TryGetEntry(EventType.OnInitAsyncAsync, out var entryAsync))
|
|
|
+ {
|
|
|
+ var result = await entryAsync.EventAction();
|
|
|
+ LogResult(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnParametersSet()
|
|
|
+ {
|
|
|
+ if (TryGetEntry(EventType.OnParametersSet, out var entry))
|
|
|
+ {
|
|
|
+ var result = entry.EventAction();
|
|
|
+ LogResult(result.Result);
|
|
|
+ }
|
|
|
+ base.OnParametersSet();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override async Task OnParametersSetAsync()
|
|
|
+ {
|
|
|
+ if (TryGetEntry(EventType.OnParametersSetAsyncSync, out var entrySync))
|
|
|
+ {
|
|
|
+ var result = await entrySync.EventAction();
|
|
|
+ LogResult(result);
|
|
|
+
|
|
|
+ await entrySync.EventAction();
|
|
|
+ }
|
|
|
+ else if (TryGetEntry(EventType.OnParametersSetAsyncAsync, out var entryAsync))
|
|
|
+ {
|
|
|
+ var result = await entryAsync.EventAction();
|
|
|
+ LogResult(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void BuildRenderTree(RenderTreeBuilder builder)
|
|
|
+ {
|
|
|
+ base.BuildRenderTree(builder);
|
|
|
+ var renderFactory = WhatToRender[TestId];
|
|
|
+ renderFactory(this)(builder);
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool TryGetEntry(EventType eventType, out ExecutionAction entry)
|
|
|
+ {
|
|
|
+ var entries = EventActions[TestId];
|
|
|
+ if (entries == null)
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException("Failed to find entries for component with Id: " + TestId);
|
|
|
+ }
|
|
|
+ entry = entries.FirstOrDefault(e => e.Event == eventType);
|
|
|
+ return entry != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void LogResult((int, EventType) entry)
|
|
|
+ {
|
|
|
+ Log.Enqueue(entry);
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ExecutionAction
|
|
|
+ {
|
|
|
+ public EventType Event { get; set; }
|
|
|
+ public Func<Task<(int id, EventType @event)>> EventAction { get; set; }
|
|
|
+
|
|
|
+ public static ExecutionAction On(int id, EventType @event, bool async = false)
|
|
|
+ {
|
|
|
+ if (!async)
|
|
|
+ {
|
|
|
+ return new ExecutionAction
|
|
|
+ {
|
|
|
+ Event = @event,
|
|
|
+ EventAction = () => Task.FromResult((id, @event))
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return new ExecutionAction
|
|
|
+ {
|
|
|
+ Event = @event,
|
|
|
+ EventAction = async () =>
|
|
|
+ {
|
|
|
+ await Task.Yield();
|
|
|
+ return (id, @event);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public enum EventType
|
|
|
+ {
|
|
|
+ OnInit,
|
|
|
+ OnInitAsyncSync,
|
|
|
+ OnInitAsyncAsync,
|
|
|
+ OnParametersSet,
|
|
|
+ OnParametersSetAsyncSync,
|
|
|
+ OnParametersSetAsyncAsync
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|