public void ThrowsIfAssigningUnknownPropertiesToChildComponents()
        {
            // Arrange
            var testObject = new object();

            newTree.OpenComponent <FakeComponent>(0);
            newTree.AddAttribute(1, "SomeUnknownProperty", 123);
            newTree.CloseComponent();

            // Act/Assert
            var ex = Assert.Throws <InvalidOperationException>(() =>
            {
                RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, oldTree.GetFrames(), newTree.GetFrames());
            });

            Assert.Equal($"Component of type '{typeof(FakeComponent).FullName}' does not have a property matching the name 'SomeUnknownProperty'.", ex.Message);
        }
Example #2
0
        public void DisposeInBatch(RenderBatchBuilder batchBuilder)
        {
            _componentWasDisposed = true;

            // TODO: Handle components throwing during dispose. Shouldn't break the whole render batch.
            if (Component is IDisposable disposable)
            {
                disposable.Dispose();
            }

            RenderTreeDiffBuilder.DisposeFrames(batchBuilder, CurrrentRenderTree.GetFrames());

            if (_hasAnyCascadingParameterSubscriptions)
            {
                RemoveCascadingParameterSubscriptions();
            }
        }
        public void ThrowsIfAssigningReadOnlyPropertiesToChildComponents()
        {
            // Arrange
            var testObject = new object();

            newTree.OpenComponent <FakeComponent>(0);
            newTree.AddAttribute(1, nameof(FakeComponent.ReadonlyProperty), 123);
            newTree.CloseComponent();

            // Act/Assert
            var ex = Assert.Throws <InvalidOperationException>(() =>
            {
                RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, oldTree.GetFrames(), newTree.GetFrames());
            });

            Assert.StartsWith($"Unable to set property '{nameof(FakeComponent.ReadonlyProperty)}' on " +
                              $"component of type '{typeof(FakeComponent).FullName}'.", ex.Message);
        }
Example #4
0
    public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, out Exception?renderFragmentException)
    {
        renderFragmentException = null;

        // A component might be in the render queue already before getting disposed by an
        // earlier entry in the render queue. In that case, rendering is a no-op.
        if (_componentWasDisposed)
        {
            return;
        }

        _nextRenderTree.Clear();

        try
        {
            renderFragment(_nextRenderTree);
        }
        catch (Exception ex)
        {
            // If an exception occurs in the render fragment delegate, we won't process the diff in any way, so child components,
            // event handlers, etc., will all be left untouched as if this component didn't re-render at all. The Renderer will
            // then forcibly clear the descendant subtree by rendering an empty fragment for this component.
            renderFragmentException = ex;
            return;
        }

        // We don't want to make errors from this be recoverable, because there's no legitimate reason for them to happen
        _nextRenderTree.AssertTreeIsValid(Component);

        // Swap the old and new tree builders
        (CurrentRenderTree, _nextRenderTree) = (_nextRenderTree, CurrentRenderTree);

        var diff = RenderTreeDiffBuilder.ComputeDiff(
            _renderer,
            batchBuilder,
            ComponentId,
            _nextRenderTree.GetFrames(),
            CurrentRenderTree.GetFrames());

        batchBuilder.UpdatedComponentDiffs.Append(diff);
        batchBuilder.InvalidateParameterViews();
    }
Example #5
0
        public void RenderIntoBatch(RenderBatchBuilder batchBuilder)
        {
            if (_component is IHandlePropertiesChanged notifyableComponent)
            {
                notifyableComponent.OnPropertiesChanged();
            }

            // Swap the old and new tree builders
            (_renderTreeBuilderCurrent, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, _renderTreeBuilderCurrent);

            _renderTreeBuilderCurrent.Clear();
            _component.BuildRenderTree(_renderTreeBuilderCurrent);

            var diff = RenderTreeDiffBuilder.ComputeDiff(
                _renderer,
                batchBuilder,
                _componentId,
                _renderTreeBuilderPrevious.GetFrames(),
                _renderTreeBuilderCurrent.GetFrames());

            batchBuilder.UpdatedComponentDiffs.Append(diff);
        }
Example #6
0
        public void SkipsUpdatingParametersOnChildComponentsIfAllAreDefinitelyImmutableAndUnchanged()
        {
            // We only know that types are immutable if either Type.IsPrimitive, or it's one of
            // a known set of common immutable types.

            // Arrange: Populate old and new with equivalent content
            RenderFragment fragmentWillNotChange = builder => throw new NotImplementedException();
            var            dateTimeWillNotChange = DateTime.Now;

            foreach (var tree in new[] { oldTree, newTree })
            {
                tree.OpenComponent <CaptureSetParametersComponent>(0);
                tree.AddAttribute(1, "MyString", "Some fixed string");
                tree.AddAttribute(1, "MyByte", (byte)123);
                tree.AddAttribute(1, "MyInt", int.MaxValue);
                tree.AddAttribute(1, "MyLong", long.MaxValue);
                tree.AddAttribute(1, "MyBool", true);
                tree.AddAttribute(1, "MyFloat", float.MaxValue);
                tree.AddAttribute(1, "MyDouble", double.MaxValue);
                tree.AddAttribute(1, "MyDecimal", decimal.MinusOne);
                tree.AddAttribute(1, "MyDate", dateTimeWillNotChange);
                tree.AddAttribute(1, "MyFragment", fragmentWillNotChange); // Treat fragments as primitive
                tree.CloseComponent();
            }

            RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
            var originalComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;

            Assert.Equal(1, originalComponentInstance.SetParametersCallCount);

            // Act
            var renderBatch          = GetRenderedBatch();
            var newComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;

            // Assert
            Assert.Same(originalComponentInstance, newComponentInstance);
            Assert.Equal(1, originalComponentInstance.SetParametersCallCount); // Received no further parameter change notification
        }
        public void QueuesRemovedChildComponentsForDisposal()
        {
            // Arrange
            oldTree.OpenComponent <DisposableComponent>(10);      // <DisposableComponent>
            oldTree.CloseComponent();                             // </DisposableComponent>
            oldTree.OpenComponent <NonDisposableComponent>(20);   // <NonDisposableComponent>
            oldTree.CloseComponent();                             // </NonDisposableComponent>
            oldTree.OpenComponent <DisposableComponent>(30);      // <DisposableComponent>
            oldTree.CloseComponent();                             // </DisposableComponent>
            newTree.OpenComponent <DisposableComponent>(30);      // <DisposableComponent>
            newTree.CloseComponent();                             // </DisposableComponent>

            var batchBuilder = new RenderBatchBuilder();

            RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());

            // Act/Assert
            // Note that we track NonDisposableComponent was disposed even though it's not IDisposable,
            // because it's up to the upstream renderer to decide what "disposing" a component means
            Assert.Empty(batchBuilder.ComponentDisposalQueue);
            RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());
            Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue);
        }
        public void RecognizesComponentTypeChangesAtSameSequenceNumber()
        {
            // Arrange
            oldTree.OpenComponent <FakeComponent>(123);
            oldTree.CloseComponent();
            GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
            newTree.OpenComponent <FakeComponent2>(123);
            newTree.CloseComponent();
            var batchBuilder = new RenderBatchBuilder();

            // Act
            var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());

            // Assert: We're going to dispose the old component and render the new one
            Assert.Equal(new[] { 0 }, batchBuilder.ComponentDisposalQueue);
            Assert.Collection(diff.Edits,
                              entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 0),
                              entry =>
            {
                AssertEdit(entry, RenderTreeEditType.PrependFrame, 0);
                Assert.IsType <FakeComponent2>(batchBuilder.ReferenceFramesBuffer.Buffer[entry.ReferenceFrameIndex].Component);
            });
        }
Example #9
0
        public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
        {
            // A component might be in the render queue already before getting disposed by an
            // earlier entry in the render queue. In that case, rendering is a no-op.
            if (_componentWasDisposed)
            {
                return;
            }

            // Swap the old and new tree builders
            (_renderTreeBuilderCurrent, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, _renderTreeBuilderCurrent);

            _renderTreeBuilderCurrent.Clear();
            renderFragment(_renderTreeBuilderCurrent);

            var diff = RenderTreeDiffBuilder.ComputeDiff(
                _renderer,
                batchBuilder,
                _componentId,
                _renderTreeBuilderPrevious.GetFrames(),
                _renderTreeBuilderCurrent.GetFrames());

            batchBuilder.UpdatedComponentDiffs.Append(diff);
        }