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