From 848f24536a61dc28b5f40dfa2b3ff2b95c71e301 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 14 Feb 2018 22:45:19 +0000 Subject: [PATCH] Support "Region" frames in diffing --- .../RenderTree/RenderTreeDiffBuilder.cs | 31 +++++ .../RenderTreeDiffBuilderTest.cs | 127 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs index 14472b136c..90ea174566 100644 --- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs @@ -247,6 +247,15 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree break; } + case RenderTreeFrameType.Region: + { + AppendDiffEntriesForRange( + ref diffContext, + oldFrameIndex + 1, oldFrameIndex + oldFrame.RegionSubtreeLength, + newFrameIndex + 1, newFrameIndex + newFrame.RegionSubtreeLength); + break; + } + case RenderTreeFrameType.Component: { if (oldFrame.ComponentType == newFrame.ComponentType) @@ -328,6 +337,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree diffContext.SiblingIndex++; break; } + case RenderTreeFrameType.Region: + { + var regionChildFrameIndex = newFrameIndex + 1; + var regionChildFrameEndIndexExcl = newFrameIndex + newFrame.RegionSubtreeLength; + while (regionChildFrameIndex < regionChildFrameEndIndexExcl) + { + InsertNewFrame(ref diffContext, regionChildFrameIndex); + regionChildFrameIndex = NextSiblingIndex(newTree[regionChildFrameIndex], regionChildFrameIndex); + } + break; + } case RenderTreeFrameType.Text: { var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame); @@ -361,6 +381,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex)); break; } + case RenderTreeFrameType.Region: + { + var regionChildFrameIndex = oldFrameIndex + 1; + var regionChildFrameEndIndexExcl = oldFrameIndex + oldFrame.RegionSubtreeLength; + while (regionChildFrameIndex < regionChildFrameEndIndexExcl) + { + RemoveOldFrame(ref diffContext, regionChildFrameIndex); + regionChildFrameIndex = NextSiblingIndex(oldTree[regionChildFrameIndex], regionChildFrameIndex); + } + break; + } case RenderTreeFrameType.Text: { diffContext.Edits.Append(RenderTreeEdit.RemoveFrame(diffContext.SiblingIndex)); diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs index 847a484b4f..dfd9ec4ac1 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs @@ -579,6 +579,133 @@ namespace Microsoft.AspNetCore.Blazor.Test AssertFrame.Text(referenceFrames[0], "text2modified", 11); } + [Fact] + public void PassesThroughRegionsInsidePrependedElements() + { + // Arrange + oldTree.AddText(0, "Will not change"); + newTree.AddText(0, "Will not change"); + newTree.OpenElement(1, "root"); + newTree.OpenRegion(2); + newTree.AddText(0, "text1"); + newTree.CloseRegion(); + newTree.CloseElement(); + + // Act + var (result, referenceFrames) = GetSingleUpdatedComponent(); + + // Assert + Assert.Collection(result.Edits, + entry => + { + AssertEdit(entry, RenderTreeEditType.PrependFrame, 1); + Assert.Equal(0, entry.ReferenceFrameIndex); + }); + Assert.Collection(referenceFrames, + frame => AssertFrame.Element(frame, "root", 3, 1), + frame => AssertFrame.Region(frame, 2, 2), + frame => AssertFrame.Text(frame, "text1")); + } + + [Fact] + public void RecognizesInsertedRegions() + { + // Arrange + oldTree.AddText(1, "Start"); + oldTree.AddText(3, "End"); + newTree.AddText(1, "Start"); + newTree.OpenRegion(2); + newTree.AddText(4, "Text inside region"); // Sequence number is unrelated to outside the region + newTree.OpenRegion(5); + newTree.AddText(6, "Text inside nested region"); + newTree.CloseRegion(); + newTree.CloseRegion(); + newTree.AddText(3, "End"); + + // Act + var (result, referenceFrames) = GetSingleUpdatedComponent(); + + // Assert + Assert.Collection(result.Edits, + entry => + { + AssertEdit(entry, RenderTreeEditType.PrependFrame, 1); + AssertFrame.Text( + referenceFrames[entry.ReferenceFrameIndex], "Text inside region"); + }, + entry => + { + AssertEdit(entry, RenderTreeEditType.PrependFrame, 2); + AssertFrame.Text( + referenceFrames[entry.ReferenceFrameIndex], "Text inside nested region"); + }); + } + + [Fact] + public void RecognizesRemovedRegions() + { + // Arrange + oldTree.AddText(1, "Start"); + oldTree.OpenRegion(2); + oldTree.AddText(4, "Text inside region"); // Sequence number is unrelated to outside the region + oldTree.OpenRegion(5); + oldTree.AddText(6, "Text inside nested region"); + oldTree.CloseRegion(); + oldTree.CloseRegion(); + oldTree.AddText(3, "End"); + newTree.AddText(1, "Start"); + newTree.AddText(3, "End"); + + // Act + var (result, referenceFrames) = GetSingleUpdatedComponent(); + + // Assert + Assert.Collection(result.Edits, + entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 1), + entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 1)); + } + + [Fact] + public void RecognizesEquivalentRegions() + { + // Arrange + oldTree.AddText(1, "Start"); + oldTree.OpenRegion(2); + oldTree.AddText(4, "Text inside region"); + oldTree.AddText(5, "Text to move"); + oldTree.OpenRegion(6); + oldTree.CloseRegion(); + oldTree.CloseRegion(); + oldTree.AddText(3, "End"); + newTree.AddText(1, "Start"); + newTree.OpenRegion(2); + newTree.AddText(4, "Changed text inside region"); + newTree.OpenRegion(6); + newTree.AddText(5, "Text to move"); // Although it's the same sequence and content, it's now in a different region so not the same + newTree.CloseRegion(); + newTree.CloseRegion(); + newTree.AddText(3, "End"); + + // Act + var (result, referenceFrames) = GetSingleUpdatedComponent(); + + // Assert + Assert.Collection(result.Edits, + entry => + { + AssertEdit(entry, RenderTreeEditType.UpdateText, 1); + AssertFrame.Text( + referenceFrames[entry.ReferenceFrameIndex], "Changed text inside region"); + }, + entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 2), + entry => + { + AssertEdit(entry, RenderTreeEditType.PrependFrame, 2); + AssertFrame.Text( + referenceFrames[entry.ReferenceFrameIndex], "Text to move"); + }); + } + [Fact] public void InstantiatesChildComponentsForInsertedFrames() {