Пример #1
0
        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");
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }
Пример #4
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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        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");
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
            });
        }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }
Пример #13
0
        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));
        }
Пример #14
0
        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);
        }
Пример #15
0
        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");
            });
        }
Пример #16
0
        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);
        }
Пример #17
0
        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");
            });
        }
Пример #18
0
        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);
        }
Пример #19
0
        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);
        }
Пример #20
0
        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");
                });
            });
        }
Пример #21
0
        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
        }