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 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); }
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(); }
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); }
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); }); }
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); }