public void ParameterViewSuppliedWithCascadingParametersCannotBeUsedAfterSynchronousReturn() { // Arrange var providedValue = "Initial value"; var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", providedValue); builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.CloseComponent(); })); builder.CloseComponent(); }); // Initial render; capture nested component var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId); // Re-render CascadingValue with new value, so it gets a new ParameterView providedValue = "Updated value"; component.TriggerRender(); Assert.Equal(2, renderer.Batches.Count); // It's no longer able to access anything in the ParameterView it just received var ex = Assert.Throws <InvalidOperationException>(nestedComponent.AttemptIllegalAccessToLastParameterView); Assert.Equal($"The {nameof(ParameterView)} instance can no longer be read because it has expired. {nameof(ParameterView)} can only be read synchronously and must not be stored for later use.", ex.Message); }
public void WithCascadedAuthenticationState_DoesNotWrapOutputInCascadingAuthenticationState() { // Arrange var routeData = new RouteData(typeof(TestPageWithNoAuthorization), EmptyParametersDictionary); var rootComponent = new AuthorizeRouteViewWithExistingCascadedAuthenticationState( _authenticationStateProvider.CurrentAuthStateTask, routeData); var rootComponentId = _renderer.AssignRootComponentId(rootComponent); // Act _renderer.RenderRootComponent(rootComponentId); // Assert var batch = _renderer.Batches.Single(); var componentInstances = batch.ReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Component) .Select(f => f.Component); Assert.Collection(componentInstances, // This is the externally-supplied cascading value component => Assert.IsType <CascadingValue <Task <AuthenticationState> > >(component), component => Assert.IsType <AuthorizeRouteView>(component), // This is the hierarchy inside the AuthorizeRouteView. It doesn't contain a // further CascadingAuthenticationState component => Assert.IsAssignableFrom <AuthorizeViewCore>(component), component => Assert.IsType <LayoutView>(component), component => Assert.IsType <TestPageWithNoAuthorization>(component)); }
public void CascadingValueThrowsIfFixedFlagChangesToFalse() { // Arrange var renderer = new TestRenderer(); var isFixed = true; var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <object> >(0); if (isFixed) // Showing also that "unset" is treated as "false" { builder.AddAttribute(1, "IsFixed", true); } builder.AddAttribute(2, "Value", new object()); builder.CloseComponent(); }); renderer.AssignRootComponentId(component); component.TriggerRender(); // Act/Assert isFixed = false; var ex = Assert.Throws <InvalidOperationException>(() => component.TriggerRender()); Assert.Equal("The value of IsFixed cannot be changed dynamically.", ex.Message); }
public async Task DoesNotRenderAfterOnInitAsyncTaskIsCancelledUsingCancellationToken() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent() { Counter = 1 }; var cts = new CancellationTokenSource(); cts.Cancel(); component.OnInitAsyncLogic = async _ => { await Task.Yield(); cts.Token.ThrowIfCancellationRequested(); }; // Act var componentId = renderer.AssignRootComponentId(component); await renderer.RenderRootComponentAsync(componentId); // Assert // At least one call to StateHasChanged depending on how OnInitAsyncLogic gets scheduled. Assert.NotEmpty(renderer.Batches); }
public async Task DoesNotRenderAfterOnParametersSetAsyncTaskIsCanceled() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent() { Counter = 1 }; var onParametersSetTask = new TaskCompletionSource <object>(); component.OnParametersSetAsyncLogic = _ => onParametersSetTask.Task; // Act var componentId = renderer.AssignRootComponentId(component); var renderTask = renderer.RenderRootComponentAsync(componentId); // Assert Assert.Single(renderer.Batches); // Cancel task started by OnParametersSet component.Counter = 2; onParametersSetTask.SetCanceled(); await renderTask; // Component should not be rendered again Assert.Single(renderer.Batches); }
public void SuppliesSynchronouslyAvailableAuthStateToChildContent() { // Arrange: Service var services = new ServiceCollection(); var authStateProvider = new TestAuthStateProvider() { CurrentAuthStateTask = Task.FromResult(CreateAuthenticationState("Bert")) }; services.AddSingleton <AuthenticationStateProvider>(authStateProvider); // Arrange: Renderer and component var renderer = new TestRenderer(services.BuildServiceProvider()); var component = new UseCascadingAuthenticationStateComponent(); // Act renderer.AssignRootComponentId(component); component.TriggerRender(); // Assert var batch = renderer.Batches.Single(); var receiveAuthStateId = batch.GetComponentFrames <ReceiveAuthStateComponent>().Single().ComponentId; var receiveAuthStateDiff = batch.DiffsByComponentId[receiveAuthStateId].Single(); Assert.Collection(receiveAuthStateDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Authenticated: True; Name: Bert; Pending: False; Renders: 1"); }); }
public async Task Virtualize_DispatchesExceptionsFromItemsProviderThroughRenderer() { Virtualize <int> renderedVirtualize = null; var rootComponent = new VirtualizeTestHostcomponent { InnerContent = BuildVirtualize(10f, AlwaysThrowsItemsProvider <int>, null, virtualize => renderedVirtualize = virtualize) }; var serviceProvider = new ServiceCollection() .AddTransient((sp) => Mock.Of <IJSRuntime>()) .BuildServiceProvider(); var testRenderer = new TestRenderer(serviceProvider); var componentId = testRenderer.AssignRootComponentId(rootComponent); // Render to populate the component reference. await testRenderer.RenderRootComponentAsync(componentId); Assert.NotNull(renderedVirtualize); // Simulate a JS spacer callback. ((IVirtualizeJsCallbacks)renderedVirtualize).OnAfterSpacerVisible(10f, 50f, 100f); // Validate that the exception is dispatched through the renderer. var ex = await Assert.ThrowsAsync <InvalidOperationException>(async() => await testRenderer.RenderRootComponentAsync(componentId)); Assert.Equal("Thrown from items provider.", ex.Message); }
public async Task RespondsToValidationStateChangeNotifications() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid var batch1 = renderer.Batches.Single(); var componentFrame1 = batch1.GetComponentFrames <TestInputComponent <string> >().Single(); var inputComponentId = componentFrame1.ComponentId; var component = (TestInputComponent <string>)componentFrame1.Component; Assert.Equal("valid", component.CssClass); // Act: update the field state in the EditContext and notify var messageStore = new ValidationMessageStore(rootComponent.EditContext); messageStore.Add(fieldIdentifier, "Some message"); await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged); // Assert: The input component rendered itself again and now has the new class var batch2 = renderer.Batches.Skip(1).Single(); Assert.Equal(inputComponentId, batch2.DiffsByComponentId.Keys.Single()); Assert.Equal("invalid", component.CssClass); }
public void RespondsToChangeInAuthorizationState() { // Arrange var renderer = new TestRenderer(); var rootComponent = WrapInAuthorizeView( childContent: context => builder => builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}")); rootComponent.AuthenticationState = CreateAuthenticationState("Nellie"); // Render in initial state. From other tests, we know this renders // a single batch with the correct output. renderer.AssignRootComponentId(rootComponent); rootComponent.TriggerRender(); var authorizeViewComponentId = renderer.Batches.Single() .GetComponentFrames <AuthorizeView>().Single().ComponentId; // Act rootComponent.AuthenticationState = CreateAuthenticationState("Ronaldo"); rootComponent.TriggerRender(); // Assert: It's only one new diff. We skip the intermediate "await" render state // because the task was completed synchronously. Assert.Equal(2, renderer.Batches.Count); var batch = renderer.Batches.Last(); var diff = batch.DiffsByComponentId[authorizeViewComponentId].Single(); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "You are authenticated as Ronaldo"); }); }
public async Task UserSpecifiedAriaValueIsNotChangedIfInvalid() { // Arrange// Arrange var model = new TestModel(); var invalidContext = new EditContext(model); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = invalidContext, ValueExpression = () => model.StringProperty }; rootComponent.AdditionalAttributes = new Dictionary <string, object>(); rootComponent.AdditionalAttributes["aria-invalid"] = "userSpecifiedValue"; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var messageStore = new ValidationMessageStore(invalidContext); messageStore.Add(fieldIdentifier, "Test error message"); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid var batch1 = renderer.Batches.Single(); var componentFrame1 = batch1.GetComponentFrames <TestInputComponent <string> >().Single(); var inputComponentId = componentFrame1.ComponentId; var component = (TestInputComponent <string>)componentFrame1.Component; Assert.Equal("invalid", component.CssClass); Assert.NotNull(component.AdditionalAttributes); Assert.Equal(1, component.AdditionalAttributes.Count); Assert.Equal("userSpecifiedValue", component.AdditionalAttributes["aria-invalid"]); }
public async Task AriaAttributeIsRenderedWhenTheValidationStateIsInvalidOnFirstRender() { // Arrange// Arrange var model = new TestModel(); var invalidContext = new EditContext(model); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = invalidContext, ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var messageStore = new ValidationMessageStore(invalidContext); messageStore.Add(fieldIdentifier, "Test error message"); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid var batch1 = renderer.Batches.Single(); var componentFrame1 = batch1.GetComponentFrames <TestInputComponent <string> >().Single(); var inputComponentId = componentFrame1.ComponentId; var component = (TestInputComponent <string>)componentFrame1.Component; Assert.Equal("invalid", component.CssClass); Assert.NotNull(component.AdditionalAttributes); Assert.Equal(1, component.AdditionalAttributes.Count); //Check for "true" see https://www.w3.org/TR/wai-aria-1.1/#aria-invalid Assert.Equal("true", component.AdditionalAttributes["aria-invalid"]); }
public void RendersNotAuthorizedContentIfNotAuthorized() { // Arrange var renderer = new TestRenderer(); var rootComponent = WrapInAuthorizeView( childContent: context => builder => builder.AddContent(0, "This should not be rendered"), notAuthorizedContent: builder => builder.AddContent(0, "You are not authorized")); // Act renderer.AssignRootComponentId(rootComponent); rootComponent.TriggerRender(); // Assert var diff = renderer.Batches.Single().GetComponentDiffs <AuthorizeView>().Single(); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex], "You are not authorized"); }); }
public async Task RendersAfterParametersSetAsyncTaskIsCompleted() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(); component.Counter = 1; var parametersSetTask = new TaskCompletionSource <bool>(); component.RunsBaseOnParametersSetAsync = false; component.OnParametersSetAsyncLogic = c => parametersSetTask.Task; // Act var componentId = renderer.AssignRootComponentId(component); var renderTask = renderer.RenderRootComponentAsync(componentId); // Assert Assert.Single(renderer.Batches); // Completes task started by OnParametersSetAsync component.Counter = 2; parametersSetTask.SetResult(true); await renderTask; // Component should be rendered again Assert.Equal(2, renderer.Batches.Count); }
public async Task DoesNotRenderAfterOnInitAsyncTaskIsCancelled() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent() { Counter = 1 }; var initTask = new TaskCompletionSource <object>(); component.OnInitAsyncLogic = _ => initTask.Task; // Act var componentId = renderer.AssignRootComponentId(component); var renderTask = renderer.RenderRootComponentAsync(componentId); // Assert Assert.False(renderTask.IsCompleted); Assert.Single(renderer.Batches); // Cancel task started by OnInitAsync component.Counter = 2; initTask.SetCanceled(); await renderTask; // Component should only be rendered again due to // the call to StateHasChanged after SetParametersAsync Assert.Equal(2, renderer.Batches.Count); }
public void DoesNotRenderAfterOnInitAsyncTaskIsCancelled() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent() { Counter = 1 }; var initTask = new TaskCompletionSource <object>(); component.OnInitAsyncLogic = _ => initTask.Task; // Act var componentId = renderer.AssignRootComponentId(component); renderer.RenderRootComponent(componentId); // Assert Assert.Single(renderer.Batches); // Cancel task started by OnInitAsync component.Counter = 2; initTask.SetCanceled(); // Component should not be rendered again Assert.Single(renderer.Batches); }
public async Task UnsubscribesFromValidationStateChangeNotifications() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); var component = renderer.Batches.Single().GetComponentFrames <TestInputComponent <string> >().Single().Component; // Act: dispose, then update the field state in the EditContext and notify ((IDisposable)component).Dispose(); var messageStore = new ValidationMessageStore(rootComponent.EditContext); messageStore.Add(fieldIdentifier, "Some message"); await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged); // Assert: No additional render Assert.Empty(renderer.Batches.Skip(1)); }
public void RendersChildContentIfAuthorized() { // Arrange var renderer = new TestRenderer(); var rootComponent = WrapInAuthorizeView( childContent: context => builder => builder.AddContent(0, $"You are authenticated as {context.User.Identity.Name}")); rootComponent.AuthenticationState = CreateAuthenticationState("Nellie"); // Act renderer.AssignRootComponentId(rootComponent); rootComponent.TriggerRender(); // Assert var diff = renderer.Batches.Single().GetComponentDiffs <AuthorizeView>().Single(); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( renderer.Batches.Single().ReferenceFrames[edit.ReferenceFrameIndex], "You are authenticated as Nellie"); }); }
public void StopsNotifyingDescendantsIfTheyAreRemoved() { // Arrange var providedValue = "Initial value"; var displayNestedComponent = true; var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", providedValue); builder.AddAttribute(2, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder => { if (displayNestedComponent) { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", "Goodbye"); childBuilder.CloseComponent(); } })); builder.CloseComponent(); }); // Act 1: Initial render; capture nested component ID var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId); Assert.Equal(1, nestedComponent.NumSetParametersCalls); Assert.Equal(1, nestedComponent.NumRenders); // Act/Assert 2: Re-render the CascadingValue; observe nested component wasn't re-rendered providedValue = "Updated value"; displayNestedComponent = false; // Remove the nested componet component.TriggerRender(); // Assert: We did not render the nested component now it's been removed Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; Assert.Equal(1, nestedComponent.NumRenders); Assert.Equal(2, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue, but not nested one // We *did* send updated params during the first render where it was removed, // because the params are sent before the disposal logic runs. We could avoid // this by moving the notifications into the OnAfterRender phase, but then we'd // often render descendants twice (once because they are descendants and some // direct parameter might have changed, then once because a cascading parameter // changed). We can't have it both ways, so optimize for the case when the // nested component *hasn't* just been removed. Assert.Equal(2, nestedComponent.NumSetParametersCalls); // Act 3: However, after disposal, the subscription is removed, so we won't send // updated params on subsequent CascadingValue renders. providedValue = "Updated value 2"; component.TriggerRender(); Assert.Equal(2, nestedComponent.NumSetParametersCalls); }
private static async Task <IEnumerable <TestInputRadio> > RenderAndGetTestInputComponentAsync(TestInputRadioHostComponent <TestEnum> rootComponent) { var testRenderer = new TestRenderer(); var componentId = testRenderer.AssignRootComponentId(rootComponent); await testRenderer.RenderRootComponentAsync(componentId); return(FindInputRadioComponents(testRenderer.Batches.Single())); }
private static async Task <TComponent> RenderAndGetTestInputComponentAsync <TValue, TComponent>(TestInputHostComponent <TValue, TComponent> hostComponent) where TComponent : TestInputComponent <TValue> { var testRenderer = new TestRenderer(); var componentId = testRenderer.AssignRootComponentId(hostComponent); await testRenderer.RenderRootComponentAsync(componentId); return(FindComponent <TComponent>(testRenderer.Batches.Single())); }
private static async Task <EditForm> RenderAndGetTestEditFormComponentAsync(TestEditFormHostComponent hostComponent) { var testRenderer = new TestRenderer(); var componentId = testRenderer.AssignRootComponentId(hostComponent); await testRenderer.RenderRootComponentAsync(componentId); return(FindEditFormComponent(testRenderer.Batches.Single())); }
public void RendersAuthorizingContentUntilAuthorizationCompleted() { // Arrange var @event = new ManualResetEventSlim(); var renderer = new TestRenderer() { OnUpdateDisplayComplete = () => { @event.Set(); }, }; var rootComponent = WrapInAuthorizeView( authorizingContent: builder => builder.AddContent(0, "Auth pending..."), authorizedContent: context => builder => builder.AddContent(0, $"Hello, {context.User.Identity.Name}!")); var authTcs = new TaskCompletionSource <AuthenticationState>(); rootComponent.AuthenticationState = authTcs.Task; // Act/Assert 1: Auth pending renderer.AssignRootComponentId(rootComponent); rootComponent.TriggerRender(); var batch1 = renderer.Batches.Single(); var authorizeViewComponentId = batch1.GetComponentFrames <AuthorizeView>().Single().ComponentId; var diff1 = batch1.DiffsByComponentId[authorizeViewComponentId].Single(); Assert.Collection(diff1.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch1.ReferenceFrames[edit.ReferenceFrameIndex], "Auth pending..."); }); // Act/Assert 2: Auth process completes asynchronously @event.Reset(); authTcs.SetResult(CreateAuthenticationState("Monsieur").Result); // We need to wait here because the continuations of SetResult will be scheduled to run asynchronously. @event.Wait(Timeout); Assert.Equal(2, renderer.Batches.Count); var batch2 = renderer.Batches[1]; var diff2 = batch2.DiffsByComponentId[authorizeViewComponentId].Single(); Assert.Collection(diff2.Edits, edit => { Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type); Assert.Equal(0, edit.SiblingIndex); }, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); Assert.Equal(0, edit.SiblingIndex); AssertFrame.Text( batch2.ReferenceFrames[edit.ReferenceFrameIndex], "Hello, Monsieur!"); }); }
public void SuppliesAsynchronouslyAvailableAuthStateToChildContent() { // Arrange: Service var services = new ServiceCollection(); var authStateTaskCompletionSource = new TaskCompletionSource <AuthenticationState>(); var authStateProvider = new TestAuthenticationStateProvider() { CurrentAuthStateTask = authStateTaskCompletionSource.Task }; services.AddSingleton <AuthenticationStateProvider>(authStateProvider); // Arrange: Renderer and component var renderer = new TestRenderer(services.BuildServiceProvider()); var component = new UseCascadingAuthenticationStateComponent(); // Act 1: Initial synchronous render renderer.AssignRootComponentId(component); component.TriggerRender(); // Assert 1: Empty state var batch1 = renderer.Batches.Single(); var receiveAuthStateFrame = batch1.GetComponentFrames <ReceiveAuthStateComponent>().Single(); var receiveAuthStateId = receiveAuthStateFrame.ComponentId; var receiveAuthStateComponent = (ReceiveAuthStateComponent)receiveAuthStateFrame.Component; var receiveAuthStateDiff1 = batch1.DiffsByComponentId[receiveAuthStateId].Single(); Assert.Collection(receiveAuthStateDiff1.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch1.ReferenceFrames[edit.ReferenceFrameIndex], "Authenticated: False; Name: ; Pending: True; Renders: 1"); }); // Act/Assert 2: Auth state fetch task completes in background // No new renders yet, because the cascading parameter itself hasn't changed authStateTaskCompletionSource.SetResult(CreateAuthenticationState("Bert")); Assert.Single(renderer.Batches); // Act/Assert 3: Refresh display receiveAuthStateComponent.TriggerRender(); Assert.Equal(2, renderer.Batches.Count); var batch2 = renderer.Batches.Last(); var receiveAuthStateDiff2 = batch2.DiffsByComponentId[receiveAuthStateId].Single(); Assert.Collection(receiveAuthStateDiff2.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch2.ReferenceFrames[edit.ReferenceFrameIndex], "Authenticated: True; Name: Bert; Pending: False; Renders: 2"); }); }
public async Task RunsOnAfterRenderAsync_AfterRenderingCompletes() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent() { Counter = 1 }; var onAfterRenderCompleted = false; var tcs = new TaskCompletionSource <object>(); component.OnAfterRenderAsyncLogic = async(c, firstRender) => { Assert.True(firstRender); Assert.Single(renderer.Batches); onAfterRenderCompleted = true; await tcs.Task; }; // Act var componentId = renderer.AssignRootComponentId(component); var renderTask = renderer.RenderRootComponentAsync(componentId); // Assert tcs.SetResult(null); await renderTask; Assert.True(onAfterRenderCompleted); // Component should not be rendered again. OnAfterRenderAsync doesn't do that. Assert.Single(renderer.Batches); // Act: Render again! onAfterRenderCompleted = false; tcs = new TaskCompletionSource <object>(); component.OnAfterRenderAsyncLogic = async(c, firstRender) => { Assert.False(firstRender); Assert.Equal(2, renderer.Batches.Count); onAfterRenderCompleted = true; await tcs.Task; }; renderTask = renderer.RenderRootComponentAsync(componentId); // Assert tcs.SetResult(null); await renderTask; Assert.True(onAfterRenderCompleted); Assert.Equal(2, renderer.Batches.Count); }
public async Task RendersAfterParametersSetAndInitAsyncTasksAreCompleted() { // Arrange var @event = new ManualResetEventSlim(); var renderer = new TestRenderer() { OnUpdateDisplayComplete = () => { @event.Set(); }, }; var component = new TestComponent(); component.Counter = 1; var initTask = new TaskCompletionSource <bool>(); var parametersSetTask = new TaskCompletionSource <bool>(); component.RunsBaseOnInitAsync = true; component.RunsBaseOnParametersSetAsync = true; component.OnInitAsyncLogic = c => initTask.Task; component.OnParametersSetAsyncLogic = c => parametersSetTask.Task; // Act var componentId = renderer.AssignRootComponentId(component); var renderTask = renderer.RenderRootComponentAsync(componentId); // Assert // A rendering should have happened after the synchronous execution of Init Assert.Single(renderer.Batches); @event.Reset(); // Completes task started by OnInitAsync component.Counter = 2; initTask.SetResult(true); // We need to wait here, because the continuation from SetResult needs to be scheduled. @event.Wait(Timeout); @event.Reset(); // Component should be rendered once, after set parameters Assert.Equal(2, renderer.Batches.Count); // Completes task started by OnParametersSetAsync component.Counter = 3; parametersSetTask.SetResult(false); await renderTask; Assert.True(@event.IsSet); // Component should be rendered again // after the async part of onparameterssetasync completes Assert.Equal(3, renderer.Batches.Count); }
public void RequiresTypeParameter() { var instance = new DynamicComponent(); var renderer = new TestRenderer(); var componentId = renderer.AssignRootComponentId(instance); var ex = Assert.Throws <InvalidOperationException>( () => renderer.RenderRootComponent(componentId, ParameterView.Empty)); Assert.StartsWith( $"{ nameof(DynamicComponent)} requires a non-null value for the parameter {nameof(DynamicComponent.Type)}.", ex.Message); }
public async Task ThrowsOnFirstRenderIfNoEditContextIsSupplied() { // Arrange var inputComponent = new TestInputComponent <string>(); var testRenderer = new TestRenderer(); var componentId = testRenderer.AssignRootComponentId(inputComponent); // Act/Assert var ex = await Assert.ThrowsAsync <InvalidOperationException>( () => testRenderer.RenderRootComponentAsync(componentId)); Assert.StartsWith($"{typeof(TestInputComponent<string>)} requires a cascading parameter of type {nameof(EditContext)}", ex.Message); }
public async Task ThrowsIfBothEditContextAndModelAreNull() { // Arrange var editForm = new EditForm(); var testRenderer = new TestRenderer(); var componentId = testRenderer.AssignRootComponentId(editForm); // Act/Assert var ex = await Assert.ThrowsAsync <InvalidOperationException>( () => testRenderer.RenderRootComponentAsync(componentId)); Assert.StartsWith($"{nameof(EditForm)} requires either a {nameof(EditForm.Model)} parameter, or an {nameof(EditContext)} parameter, please provide one of these.", ex.Message); }
public void ThrowsIfBothChildContentAndAuthorizedContentProvided() { // Arrange var renderer = new TestRenderer(); var rootComponent = WrapInAuthorizeView( authorizedContent: context => builder => { }, childContent: context => builder => { }); // Act/Assert renderer.AssignRootComponentId(rootComponent); var ex = Assert.Throws <InvalidOperationException>(() => rootComponent.TriggerRender()); Assert.Equal("When using AuthorizeView, do not specify both 'Authorized' and 'ChildContent'.", ex.Message); }
public void RetainsCascadingParametersWhenUpdatingDirectParameters() { // Arrange var renderer = new TestRenderer(); var regularParameterValue = "Initial value"; var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", "Hello"); builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", regularParameterValue); childBuilder.CloseComponent(); })); builder.CloseComponent(); }); // Act 1: Render in initial state var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); // Capture the nested component so we can verify the update later var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId); Assert.Equal(1, nestedComponent.NumRenders); // Act 2: Render again with updated regular parameter regularParameterValue = "Changed value"; component.TriggerRender(); // Assert Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; var nestedComponentDiff = secondBatch.DiffsByComponentId[nestedComponentId].Single(); // The nested component was rendered with the correct parameters Assert.Collection(nestedComponentDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); // This is the only change AssertFrame.Text(secondBatch.ReferenceFrames[0], "CascadingParameter=Hello; RegularParameter=Changed value"); }); Assert.Equal(2, nestedComponent.NumRenders); }