Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
javiercn committed Apr 14, 2023
1 parent 54fc180 commit d372960
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Buffers;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Components.Web.HtmlRendering;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ protected override void TrackNamedEventId(ulong eventHandlerId, int componentId,

internal Task DispatchCapturedEvent()
{
if (_capturedNamedEvent == default)
{
throw new InvalidOperationException($"No named event handler was captured for '{_formHandler}'.");
}

// Clear the list of non-streaming rendering tasks, since we've waited for quiesce before dispatching the event.
_nonStreamingPendingTasks.Clear();
return DispatchEventAsync(_capturedNamedEvent.EventHandlerId, null, EventArgs.Empty, quiesce: true);
Expand Down
163 changes: 161 additions & 2 deletions src/Components/Endpoints/test/EndpointHtmlRendererTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ public async Task CanRender_AsyncComponent()
}

[Fact]
public void DuplicateNamedEventHandlersAcrossComponentsThrows()
public void Duplicate_NamedEventHandlers_AcrossComponents_Throws()
{
// Arrange
var expectedError = @"Two different components are trying to define the same named event 'default':
Expand Down Expand Up @@ -873,6 +873,97 @@ public void RecreatedComponent_AcrossDifferentBatches_WithNamedEventHandler_Thro
Assert.Equal(expectedError, exception.Message);
}

[Fact]
public void SameComponent_WithNamedEvent_CanRenderSynchronously_MultipleTimes()
{
// Arrange
var renderer = GetEndpointHtmlRenderer();
renderer.SetFormHandlerName("default");

var component = new TestComponent(builder =>
{
builder.OpenComponent<MultiRenderNamedEventHandlerComponent>(0);
builder.CloseComponent();
});

// Act
var componentId = renderer.TestAssignRootComponentId(component);
renderer.Dispatcher.InvokeAsync(component.TriggerRender);

// Assert
Assert.Equal(2, renderer.TrackedNamedEvents.Count);
}

[Fact]
public async Task SameComponent_WithNamedEvent_CanRenderAsynchronously_MultipleTimes()
{
// Arrange
var renderer = GetEndpointHtmlRenderer();
renderer.SetFormHandlerName("default");
var continueTaskCompletion = new TaskCompletionSource();

// Act
var result = await renderer.Dispatcher.InvokeAsync(() => renderer.BeginRenderingComponent(
typeof(MultiAsyncRenderNamedEventHandlerComponent),
ParameterView.FromDictionary(new Dictionary<string, object>
{
[nameof(MultiAsyncRenderNamedEventHandlerComponent.Continue)] = continueTaskCompletion.Task
})));

// Assert
continueTaskCompletion.SetResult();
await result.QuiescenceTask;
Assert.Equal(2, renderer.TrackedNamedEvents.Count);
}

[Fact]
public async Task CanDispatchNamedEvent_ToComponent()
{
// Arrange
var renderer = GetEndpointHtmlRenderer();
renderer.SetFormHandlerName("default");
var continueTaskCompletion = new TaskCompletionSource();
var invoked = false;
Action handler = () => invoked = true;
await renderer.Dispatcher.InvokeAsync(async () =>
{
var result = renderer.BeginRenderingComponent(
typeof(NamedEventHandlerComponent),
ParameterView.FromDictionary(new Dictionary<string, object>
{
[nameof(NamedEventHandlerComponent.Handler)] = handler
}));

await result.QuiescenceTask;

// Act
await renderer.DispatchCapturedEvent();
});

// Assert
Assert.True(invoked);
}

[Fact]
public async Task Dispatching_WhenEventHasNotBeenFound_Throws()
{
// Arrange
var renderer = GetEndpointHtmlRenderer();
renderer.SetFormHandlerName("other");

var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
renderer.Dispatcher.InvokeAsync(async () =>
{
var result = renderer.BeginRenderingComponent(typeof(NamedEventHandlerComponent), ParameterView.Empty);
await result.QuiescenceTask;

// Act
await renderer.DispatchCapturedEvent();
}));

Assert.Equal("No named event handler was captured for 'other'.", exception.Message);
}

[Fact]
public void NamedEventHandlers_DifferentComponents_SameNamedHandlerInDifferentBatches_Throws()
{
Expand Down Expand Up @@ -917,15 +1008,73 @@ public void NamedEventHandlers_DifferentComponents_SameNamedHandlerInDifferentBa

private class NamedEventHandlerComponent : ComponentBase
{
[Parameter]
public Action Handler { get; set; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "form");
builder.AddAttribute(1, "onsubmit", () => { });
builder.AddAttribute(1, "onsubmit", Handler ?? (() => { }));
builder.SetEventHandlerName("default");
builder.CloseElement();
}
}

private class MultiRenderNamedEventHandlerComponent : ComponentBase
{
private bool hasRendered;

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "form");
if (!hasRendered)
{
builder.AddAttribute(1, "onsubmit", () => { });
builder.SetEventHandlerName("default");
}
else
{
builder.AddAttribute(1, "onsubmit", () => { GC.KeepAlive(new object()); });
builder.SetEventHandlerName("default");
}
builder.CloseElement();
if (!hasRendered)
{
hasRendered = true;
StateHasChanged();
}
}
}

private class MultiAsyncRenderNamedEventHandlerComponent : ComponentBase
{
private bool hasRendered;

[Parameter] public Task Continue { get; set; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "form");
if (!hasRendered)
{
builder.AddAttribute(1, "onsubmit", () => { });
builder.SetEventHandlerName("default");
}
else
{
builder.AddAttribute(1, "onsubmit", () => { GC.KeepAlive(new object()); });
builder.SetEventHandlerName("default");
}
builder.CloseElement();
}

protected override async Task OnInitializedAsync()
{
await Continue;
hasRendered = true;
}
}

private class OtherNamedEventHandlerComponent : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
Expand Down Expand Up @@ -973,6 +1122,16 @@ internal int TestAssignRootComponentId(IComponent component)
{
return base.AssignRootComponentId(component);
}

protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventNameId)
{
TrackedNamedEvents.Add(new TrackedNamedEvent(eventHandlerId, componentId, eventNameId));
base.TrackNamedEventId(eventHandlerId, componentId, eventNameId);
}

public List<TrackedNamedEvent> TrackedNamedEvents { get; set; } = new List<TrackedNamedEvent>();

public readonly record struct TrackedNamedEvent(ulong EventHandlerId, int ComponentId, string EventNameId);
}

private HttpContext GetHttpContext(HttpContext context = null)
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#nullable enable
*REMOVED*override Microsoft.AspNetCore.Components.Forms.InputFile.OnInitialized() -> void
Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.get -> Microsoft.AspNetCore.Components.Binding.ModelBindingContext!
Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.get -> Microsoft.AspNetCore.Components.Binding.ModelBindingContext?
Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.set -> void
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer
Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(System.Type! componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) -> Microsoft.AspNetCore.Components.Web.HtmlRendering.HtmlRootComponent
Expand Down

0 comments on commit d372960

Please sign in to comment.