예제 #1
0
        public void SetsUpdatedParametersOnChildComponents()
        {
            // Arrange
            var objectWillNotChange = new object();

            oldTree.OpenComponent <FakeComponent>(12);
            oldTree.AddAttribute(13, nameof(FakeComponent.StringProperty), "String will change");
            oldTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
            oldTree.CloseComponent();
            newTree.OpenComponent <FakeComponent>(12);
            newTree.AddAttribute(13, nameof(FakeComponent.StringProperty), "String did change");
            newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
            newTree.CloseComponent();

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

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

            // Assert
            Assert.Equal(1, renderBatch.UpdatedComponents.Count); // Because the diff builder only queues child component renders; it doesn't actually perfom them itself
            Assert.Same(originalComponentInstance, newComponentInstance);
            Assert.Equal("String did change", newComponentInstance.StringProperty);
            Assert.Same(objectWillNotChange, newComponentInstance.ObjectProperty);
        }
        public void ComputeDiff_SingleFormField()
        {
            builder.Clear();
            var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, original.GetFrames(), modified.GetFrames());

            GC.KeepAlive(diff);
        }
예제 #3
0
        public void RetainsChildComponentsForExistingFrames()
        {
            // Arrange
            oldTree.AddContent(10, "text1");                    //  0: text1
            oldTree.OpenElement(11, "container");               //  1: <container>
            oldTree.OpenComponent <FakeComponent>(12);          //  2:   <FakeComponent>
            oldTree.CloseComponent();                           //       </FakeComponent>
            oldTree.OpenComponent <FakeComponent2>(13);         //  3:   <FakeComponent2>
            oldTree.CloseComponent();                           //       </FakeComponent2>
            oldTree.CloseElement();                             //     </container>
            newTree.AddContent(10, "text1");                    //  0: text1
            newTree.OpenElement(11, "container");               //  1: <container>
            newTree.OpenComponent <FakeComponent>(12);          //  2:   <FakeComponent>
            newTree.CloseComponent();                           //       </FakeComponent>
            newTree.OpenComponent <FakeComponent2>(13);         //  3:   <FakeComponent2>
            newTree.CloseComponent();                           //       </FakeComponent2>
            newTree.CloseElement();                             //     </container>

            RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
            var originalFakeComponentInstance  = oldTree.GetFrames().Array[2].Component;
            var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component;

            // Act
            var(result, referenceFrames) = GetSingleUpdatedComponent();
            var newFrame1 = newTree.GetFrames().Array[2];
            var newFrame2 = newTree.GetFrames().Array[3];

            // Assert
            Assert.Empty(result.Edits);
            Assert.Equal(0, newFrame1.ComponentId);
            Assert.Equal(1, newFrame2.ComponentId);
            Assert.Same(originalFakeComponentInstance, newFrame1.Component);
            Assert.Same(originalFakeComponent2Instance, newFrame2.Component);
        }
예제 #4
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
            (CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree);

            CurrentRenderTree.Clear();
            renderFragment(CurrentRenderTree);

            CurrentRenderTree.AssertTreeIsValid(Component);

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

            batchBuilder.UpdatedComponentDiffs.Append(diff);
            batchBuilder.InvalidateParameterViews();
        }
예제 #5
0
        public void UpdatesChangedPropertiesOnRetainedChildComponents()
        {
            // Arrange
            var objectWillNotChange = new object();

            oldTree.OpenComponent <FakeComponent>(12);
            oldTree.AddAttribute(13, nameof(FakeComponent.StringProperty), "String will change");
            oldTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
            oldTree.CloseComponent();
            newTree.OpenComponent <FakeComponent>(12);
            newTree.AddAttribute(13, nameof(FakeComponent.StringProperty), "String did change");
            newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
            newTree.CloseComponent();

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

            originalComponentInstance.ObjectProperty = null; // So we can see it doesn't get reassigned

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

            // Assert
            Assert.Equal(1, renderBatch.UpdatedComponents.Count); // Because the diff builder only queues child component renders; it doesn't actually perfom them itself
            Assert.Same(originalComponentInstance, newComponentInstance);
            Assert.Equal("String did change", newComponentInstance.StringProperty);
            Assert.Null(newComponentInstance.ObjectProperty); // To observe that the property wasn't even written, we nulled it out on the original
        }
예제 #6
0
        public void RecognizesComponentTypeChangesAtSameSequenceNumber()
        {
            // Arrange
            oldTree.OpenComponent <FakeComponent>(123);
            oldTree.CloseComponent();
            GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree); // 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.Equal(new[] { 1 }, batchBuilder.ComponentRenderQueue);

            // Assert: Got correct info in diff
            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);
            });
        }
        private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder to)
        {
            var batchBuilder = new RenderBatchBuilder();
            var diff         = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames());

            batchBuilder.UpdatedComponentDiffs.Append(diff);
            return(batchBuilder.ToBatch());
        }
        private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder to, bool initializeFromFrames)
        {
            if (initializeFromFrames)
            {
                var emptyFrames = new RenderTreeBuilder(renderer).GetFrames();
                var oldFrames   = from.GetFrames();
                RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, emptyFrames, oldFrames);
            }

            var batchBuilder = new RenderBatchBuilder();
            var diff         = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames());

            batchBuilder.UpdatedComponentDiffs.Append(diff);
            return(batchBuilder.ToBatch());
        }
예제 #9
0
        public void RenderIntoBatch(RenderBatchBuilder batchBuilder, Action <RenderTreeBuilder> renderAction)
        {
            // Swap the old and new tree builders
            (_renderTreeBuilderCurrent, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, _renderTreeBuilderCurrent);

            _renderTreeBuilderCurrent.Clear();
            renderAction(_renderTreeBuilderCurrent);

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

            batchBuilder.UpdatedComponentDiffs.Append(diff);
        }
        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);
        }
        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);
        }
예제 #12
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();
    }
예제 #13
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);
        }
예제 #14
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);
        }