public void ReRendersChildComponentsWhenPropertiesChange() { // Arrange: First render var renderer = new TestRenderer(); var firstRender = true; var component = new TestComponent(builder => { builder.OpenComponent <MessageComponent>(1); builder.AddAttribute(2, nameof(MessageComponent.Message), firstRender ? "first" : "second"); builder.CloseComponent(); }); var rootComponentId = renderer.AssignComponentId(component); component.TriggerRender(); var childComponentId = renderer.Batches.Single() .ReferenceFrames .Single(frame => frame.FrameType == RenderTreeFrameType.Component) .ComponentId; // Act: Second render firstRender = false; component.TriggerRender(); var diff = renderer.Batches[1].DiffsByComponentId[childComponentId].Single(); // Assert Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); }); AssertFrame.Text(renderer.Batches[1].ReferenceFrames[0], "second"); }
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 void ComponentCanTriggerRenderWhenNoBatchIsInProgress() { // Arrange var renderer = new TestRenderer(); var renderCount = 0; var component = new TestComponent(builder => { builder.AddContent(0, $"Render count: {++renderCount}"); }); var componentId = renderer.AssignComponentId(component); // Act/Assert: Can trigger initial render Assert.Equal(0, renderCount); component.TriggerRender(); Assert.Equal(1, renderCount); var batch1 = renderer.Batches.Single(); var edit1 = batch1.DiffsByComponentId[componentId].Single().Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, edit1.Type); AssertFrame.Text(batch1.ReferenceFrames[edit1.ReferenceFrameIndex], "Render count: 1", 0); // Act/Assert: Can trigger subsequent render component.TriggerRender(); Assert.Equal(2, renderCount); var batch2 = renderer.Batches.Skip(1).Single(); var edit2 = batch2.DiffsByComponentId[componentId].Single().Edits.Single(); Assert.Equal(RenderTreeEditType.UpdateText, edit2.Type); AssertFrame.Text(batch2.ReferenceFrames[edit2.ReferenceFrameIndex], "Render count: 2", 0); }
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); }
public void RenderBatchIncludesListOfDisposedComponents() { // Arrange var renderer = new TestRenderer(); var firstRender = true; var component = new TestComponent(builder => { if (firstRender) { // Nested descendants builder.OpenComponent <ConditionalParentComponent <FakeComponent> >(100); builder.AddAttribute(101, nameof(ConditionalParentComponent <FakeComponent> .IncludeChild), true); builder.CloseComponent(); } builder.OpenComponent <FakeComponent>(200); builder.CloseComponent(); }); var rootComponentId = renderer.AssignComponentId(component); // Act/Assert 1: First render, capturing child component IDs component.TriggerRender(); var batch = renderer.Batches.Single(); var rootComponentDiff = batch.DiffsByComponentId[rootComponentId].Single(); var childComponentIds = rootComponentDiff .Edits .Select(edit => batch.ReferenceFrames[edit.ReferenceFrameIndex]) .Where(frame => frame.FrameType == RenderTreeFrameType.Component) .Select(frame => frame.ComponentId) .ToList(); var childComponent3 = batch.ReferenceFrames.Where(f => f.ComponentId == 3) .Single().Component; Assert.Equal(new[] { 1, 2 }, childComponentIds); Assert.IsType <FakeComponent>(childComponent3); // Act: Second render firstRender = false; component.TriggerRender(); // Assert: Applicable children are included in disposal list Assert.Equal(2, renderer.Batches.Count); Assert.Equal(new[] { 1, 3 }, renderer.Batches[1].DisposedComponentIDs); // Act/Assert: If a disposed component requests a render, it's a no-op ((FakeComponent)childComponent3).RenderHandle.Render(builder => throw new NotImplementedException("Should not be invoked")); Assert.Equal(2, renderer.Batches.Count); }
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, RenderTreeBuilder.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); }
public void CanRenderTopLevelComponents() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenElement(0, "my element"); builder.AddContent(1, "some text"); builder.CloseElement(); }); // Act var componentId = renderer.AssignComponentId(component); component.TriggerRender(); // Assert var batch = renderer.Batches.Single(); var diff = batch.DiffsByComponentId[componentId].Single(); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); }); AssertFrame.Element(batch.ReferenceFrames[0], "my element", 2); AssertFrame.Text(batch.ReferenceFrames[1], "some text"); }
public void ThrowsIfComponentDoesNotHandleEvents() { // Arrange: Render a component with an event handler var renderer = new TestRenderer(); UIEventHandler handler = args => throw new NotImplementedException(); var component = new TestComponent(builder => { builder.OpenElement(0, "mybutton"); builder.AddAttribute(1, "onclick", handler); builder.CloseElement(); }); var componentId = renderer.AssignComponentId(component); component.TriggerRender(); var eventHandlerId = renderer.Batches.Single() .ReferenceFrames .First(frame => frame.AttributeValue != null) .AttributeEventHandlerId; var eventArgs = new UIEventArgs(); // Act/Assert var ex = Assert.Throws <InvalidOperationException>(() => { renderer.DispatchEvent(componentId, eventHandlerId, eventArgs); }); Assert.Equal($"The component of type {typeof(TestComponent).FullName} cannot receive " + $"events because it does not implement {typeof(IHandleEvent).FullName}.", ex.Message); }
public void NotifiesDescendantsOfUpdatedCascadingParameterValuesAndPreservesDirectParameters() { // 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, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder => { 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.NumRenders); // Act 2: Re-render CascadingValue with new value providedValue = "Updated value"; component.TriggerRender(); // Assert: We re-rendered CascadingParameterConsumerComponent 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=Updated value; RegularParameter=Goodbye"); }); Assert.Equal(2, nestedComponent.NumRenders); }
public void QueuedRenderIsSkippedIfComponentWasAlreadyDisposedInSameBatch() { // Arrange var renderer = new TestRenderer(); var shouldRenderChild = true; TestComponent component = null; component = new TestComponent(builder => { builder.AddContent(0, "Some frame so the child isn't at position zero"); if (shouldRenderChild) { builder.OpenComponent <RendersSelfAfterEventComponent>(1); builder.AddAttribute(2, "onclick", (Action <object>)((object obj) => { // First we queue (1) a re-render of the root component, then the child component // will queue (2) its own re-render. But by the time (1) completes, the child will // have been disposed, even though (2) is still in the queue shouldRenderChild = false; component.TriggerRender(); })); builder.CloseComponent(); } }); var componentId = renderer.AssignComponentId(component); component.TriggerRender(); var childComponentId = renderer.Batches.Single() .ReferenceFrames .Where(f => f.ComponentId != 0) .Single() .ComponentId; var origEventHandlerId = renderer.Batches.Single() .ReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "onclick") .Single(f => f.AttributeEventHandlerId != 0) .AttributeEventHandlerId; // Act // The fact that there's no error here is the main thing we're testing renderer.DispatchEvent(childComponentId, origEventHandlerId, args: null); // Assert: correct render result var newBatch = renderer.Batches.Skip(1).Single(); Assert.Equal(1, newBatch.DisposedComponentIDs.Count); Assert.Equal(1, newBatch.DiffsByComponentId.Count); Assert.Collection(newBatch.DiffsByComponentId[componentId].Single().Edits, edit => { Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type); Assert.Equal(1, edit.SiblingIndex); }); }
public void ComponentCannotTriggerRenderBeforeRenderHandleAssigned() { // Arrange var component = new TestComponent(builder => { }); // Act/Assert var ex = Assert.Throws <InvalidOperationException>(() => { component.TriggerRender(); }); Assert.Equal("The render handle is not yet assigned.", ex.Message); }
public void UpdatesPropertiesOnRetainedChildComponentInstances() { // Arrange: First render, capturing child component instance var renderer = new TestRenderer(); var objectThatWillNotChange = new object(); var firstRender = true; var component = new TestComponent(builder => { builder.OpenComponent <FakeComponent>(1); builder.AddAttribute(2, nameof(FakeComponent.IntProperty), firstRender ? 123 : 256); builder.AddAttribute(3, nameof(FakeComponent.ObjectProperty), objectThatWillNotChange); builder.AddAttribute(4, nameof(FakeComponent.StringProperty), firstRender ? "String that will change" : "String that did change"); builder.CloseComponent(); }); var rootComponentId = renderer.AssignComponentId(component); component.TriggerRender(); var originalComponentFrame = renderer.Batches.Single() .ReferenceFrames .Single(frame => frame.FrameType == RenderTreeFrameType.Component); var childComponentInstance = (FakeComponent)originalComponentFrame.Component; // Assert 1: properties were assigned Assert.Equal(123, childComponentInstance.IntProperty); Assert.Equal("String that will change", childComponentInstance.StringProperty); Assert.Same(objectThatWillNotChange, childComponentInstance.ObjectProperty); // Act: Second render firstRender = false; component.TriggerRender(); // Assert Assert.Equal(256, childComponentInstance.IntProperty); Assert.Equal("String that did change", childComponentInstance.StringProperty); Assert.Same(objectThatWillNotChange, childComponentInstance.ObjectProperty); }
public void PreservesChildComponentInstancesWithNoAttributes() { // Arrange: First render, capturing child component instance var renderer = new TestRenderer(); var message = "Hello"; var component = new TestComponent(builder => { builder.AddContent(0, message); builder.OpenComponent <MessageComponent>(1); builder.CloseComponent(); }); var rootComponentId = renderer.AssignComponentId(component); component.TriggerRender(); var nestedComponentFrame = renderer.Batches.Single() .ReferenceFrames .Single(frame => frame.FrameType == RenderTreeFrameType.Component); var nestedComponentInstance = (MessageComponent)nestedComponentFrame.Component; // Act: Second render message = "Modified message"; component.TriggerRender(); // Assert var batch = renderer.Batches[1]; var diff = batch.DiffsByComponentId[rootComponentId].Single(); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); }); AssertFrame.Text(batch.ReferenceFrames[0], "Modified message"); Assert.False(batch.DiffsByComponentId.ContainsKey(nestedComponentFrame.ComponentId)); }
public void DoesNotNotifyDescendantsIfCascadingParameterValuesAreImmutableAndUnchanged() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", "Unchanging value"); builder.AddAttribute(2, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder => { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", "Goodbye"); childBuilder.CloseComponent(); })); builder.CloseComponent(); }); // Act 1: Initial render var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out _); Assert.Equal(3, firstBatch.DiffsByComponentId.Count); // Root + CascadingValue + nested Assert.Equal(1, nestedComponent.NumRenders); // Act/Assert: Re-render the CascadingValue; observe nested component wasn't re-rendered component.TriggerRender(); // Assert: We did not re-render CascadingParameterConsumerComponent Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; Assert.Equal(2, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue, but not nested one Assert.Equal(1, nestedComponent.NumRenders); }
public void CanReRenderNestedComponents() { // Arrange: parent component already rendered var renderer = new TestRenderer(); var parentComponent = new TestComponent(builder => { builder.OpenComponent <MessageComponent>(0); builder.CloseComponent(); }); var parentComponentId = renderer.AssignComponentId(parentComponent); parentComponent.TriggerRender(); var nestedComponentFrame = renderer.Batches.Single() .ReferenceFrames .Single(frame => frame.FrameType == RenderTreeFrameType.Component); var nestedComponent = (MessageComponent)nestedComponentFrame.Component; var nestedComponentId = nestedComponentFrame.ComponentId; // Assert: initial render nestedComponent.Message = "Render 1"; nestedComponent.TriggerRender(); var batch = renderer.Batches[1]; var firstDiff = batch.DiffsByComponentId[nestedComponentId].Single(); Assert.Collection(firstDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); AssertFrame.Text(batch.ReferenceFrames[0], "Render 1"); }); // Act/Assert: re-render nestedComponent.Message = "Render 2"; nestedComponent.TriggerRender(); var secondBatch = renderer.Batches[2]; var secondDiff = secondBatch.DiffsByComponentId[nestedComponentId].Single(); Assert.Collection(secondDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); AssertFrame.Text(secondBatch.ReferenceFrames[0], "Render 2"); }); }
public void CanDispatchEventsToNestedComponents() { UIEventArgs receivedArgs = null; // Arrange: Render parent component var renderer = new TestRenderer(); var parentComponent = new TestComponent(builder => { builder.OpenComponent <EventComponent>(0); builder.CloseComponent(); }); var parentComponentId = renderer.AssignComponentId(parentComponent); parentComponent.TriggerRender(); // Arrange: Render nested component var nestedComponentFrame = renderer.Batches.Single() .ReferenceFrames .Single(frame => frame.FrameType == RenderTreeFrameType.Component); var nestedComponent = (EventComponent)nestedComponentFrame.Component; nestedComponent.OnTest = args => { receivedArgs = args; }; var nestedComponentId = nestedComponentFrame.ComponentId; nestedComponent.TriggerRender(); // Find nested component's event handler ID var eventHandlerId = renderer.Batches[1] .ReferenceFrames .First(frame => frame.AttributeValue != null) .AttributeEventHandlerId; // Assert: Event not yet fired Assert.Null(receivedArgs); // Act/Assert: Event can be fired var eventArgs = new UIEventArgs(); renderer.DispatchEvent(nestedComponentId, eventHandlerId, eventArgs); Assert.Same(eventArgs, receivedArgs); }
public void CanRenderNestedComponents() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.AddContent(0, "Hello"); builder.OpenComponent <MessageComponent>(1); builder.AddAttribute(2, nameof(MessageComponent.Message), "Nested component output"); builder.CloseComponent(); }); // Act/Assert var componentId = renderer.AssignComponentId(component); component.TriggerRender(); var batch = renderer.Batches.Single(); var componentFrame = batch.ReferenceFrames .Single(frame => frame.FrameType == RenderTreeFrameType.Component); var nestedComponentId = componentFrame.ComponentId; var nestedComponentDiff = batch.DiffsByComponentId[nestedComponentId].Single(); // We rendered both components Assert.Equal(2, batch.DiffsByComponentId.Count); // The nested component exists Assert.IsType <MessageComponent>(componentFrame.Component); // The nested component was rendered as part of the batch Assert.Collection(nestedComponentDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Nested component output"); }); }
public void PassesCascadingParametersToNestedComponents() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", "Hello"); builder.AddAttribute(2, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder => { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", "Goodbye"); childBuilder.CloseComponent(); })); builder.CloseComponent(); }); // Act/Assert var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var batch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(batch, out var nestedComponentId); var nestedComponentDiff = batch.DiffsByComponentId[nestedComponentId].Single(); // The nested component was rendered with the correct parameters Assert.Collection(nestedComponentDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "CascadingParameter=Hello; RegularParameter=Goodbye"); }); Assert.Equal(1, nestedComponent.NumRenders); }
public void ComponentCanTriggerRenderWhenExistingBatchIsInProgress() { // Arrange var renderer = new TestRenderer(); TestComponent parent = null; var parentRenderCount = 0; parent = new TestComponent(builder => { builder.OpenComponent <ReRendersParentComponent>(0); builder.AddAttribute(1, nameof(ReRendersParentComponent.Parent), parent); builder.CloseComponent(); builder.AddContent(2, $"Parent render count: {++parentRenderCount}"); }); var parentComponentId = renderer.AssignComponentId(parent); // Act parent.TriggerRender(); // Assert var batch = renderer.Batches.Single(); Assert.Equal(4, batch.DiffsInOrder.Count); // First is the parent component's initial render var diff1 = batch.DiffsInOrder[0]; Assert.Equal(parentComponentId, diff1.ComponentId); Assert.Collection(diff1.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Component <ReRendersParentComponent>( batch.ReferenceFrames[edit.ReferenceFrameIndex]); }, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Parent render count: 1"); }); // Second is the child component's single render var diff2 = batch.DiffsInOrder[1]; Assert.NotEqual(parentComponentId, diff2.ComponentId); var diff2edit = diff2.Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, diff2edit.Type); AssertFrame.Text(batch.ReferenceFrames[diff2edit.ReferenceFrameIndex], "Child is here"); // Third is the parent's triggered render var diff3 = batch.DiffsInOrder[2]; Assert.Equal(parentComponentId, diff3.ComponentId); var diff3edit = diff3.Edits.Single(); Assert.Equal(RenderTreeEditType.UpdateText, diff3edit.Type); AssertFrame.Text(batch.ReferenceFrames[diff3edit.ReferenceFrameIndex], "Parent render count: 2"); // Fourth is child's rerender due to parent rendering var diff4 = batch.DiffsInOrder[3]; Assert.NotEqual(parentComponentId, diff4.ComponentId); Assert.Empty(diff4.Edits); }
public void AllRendersTriggeredSynchronouslyDuringEventHandlerAreHandledAsSingleBatch() { // Arrange: A root component with a child whose event handler explicitly queues // a re-render of both the root component and the child var renderer = new TestRenderer(); var eventCount = 0; TestComponent rootComponent = null; EventComponent childComponent = null; rootComponent = new TestComponent(builder => { builder.AddContent(0, "Child event count: " + eventCount); builder.OpenComponent <EventComponent>(1); builder.AddAttribute(2, nameof(EventComponent.OnTest), args => { eventCount++; rootComponent.TriggerRender(); childComponent.TriggerRender(); }); builder.CloseComponent(); }); var rootComponentId = renderer.AssignComponentId(rootComponent); rootComponent.TriggerRender(); var origBatchReferenceFrames = renderer.Batches.Single().ReferenceFrames; var childComponentFrame = origBatchReferenceFrames .Single(f => f.Component is EventComponent); var childComponentId = childComponentFrame.ComponentId; childComponent = (EventComponent)childComponentFrame.Component; var origEventHandlerId = origBatchReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Attribute) .Last(f => f.AttributeEventHandlerId != 0) .AttributeEventHandlerId; Assert.Single(renderer.Batches); // Act renderer.DispatchEvent(childComponentId, origEventHandlerId, args: null); // Assert Assert.Equal(2, renderer.Batches.Count); var batch = renderer.Batches.Last(); Assert.Collection(batch.DiffsInOrder, diff => { // First we triggered the root component to re-render Assert.Equal(rootComponentId, diff.ComponentId); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Child event count: 1"); }); }, diff => { // Then the root re-render will have triggered an update to the child Assert.Equal(childComponentId, diff.ComponentId); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Render count: 2"); }); }, diff => { // Finally we explicitly requested a re-render of the child Assert.Equal(childComponentId, diff.ComponentId); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Render count: 3"); }); }); }
public void DoesNotNotifyDescendantsOfUpdatedCascadingParameterValuesWhenFixed() { // Arrange var providedValue = "Initial value"; var shouldIncludeChild = true; var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", providedValue); builder.AddAttribute(2, "IsFixed", true); builder.AddAttribute(3, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder => { if (shouldIncludeChild) { 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.NumRenders); // Assert: Initial value is supplied to descendant var nestedComponentDiff = firstBatch.DiffsByComponentId[nestedComponentId].Single(); Assert.Collection(nestedComponentDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( firstBatch.ReferenceFrames[edit.ReferenceFrameIndex], "CascadingParameter=Initial value; RegularParameter=Goodbye"); }); // Act 2: Re-render CascadingValue with new value providedValue = "Updated value"; component.TriggerRender(); // Assert: We did not re-render the descendant Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; Assert.Equal(2, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue, but not nested one Assert.Equal(1, nestedComponent.NumSetParametersCalls); Assert.Equal(1, nestedComponent.NumRenders); // Act 3: Dispose shouldIncludeChild = false; component.TriggerRender(); // Assert: Absence of an exception here implies we didn't cause a problem by // trying to remove a non-existent subscription }