Browse Source

Diffing algorithm fixes

Krzysztof Krysiński 1 year ago
parent
commit
9dfe3a17d2

+ 41 - 3
src/PixiEditor.Extensions.Tests/LayoutBuilderTests.cs

@@ -81,7 +81,11 @@ public class LayoutBuilderTests
         Assert.NotNull(button.Content);
         Assert.NotNull(button.Content);
         Assert.IsType<TextBlock>(button.Content);
         Assert.IsType<TextBlock>(button.Content);
 
 
-        testStatefulElement.State.SetState(() => testStatefulElement.State.RemoveText = true);
+        testStatefulElement.State.SetState(() =>
+        {
+            testStatefulElement.State.ReplaceText = true;
+            testStatefulElement.State.ReplaceTextWith = null;
+        });
 
 
         Assert.Null(button.Content); // Old layout is updated and text is removed
         Assert.Null(button.Content); // Old layout is updated and text is removed
     }
     }
@@ -100,11 +104,43 @@ public class LayoutBuilderTests
         Assert.NotNull(button.Content);
         Assert.NotNull(button.Content);
         Assert.IsType<TextBlock>(button.Content);
         Assert.IsType<TextBlock>(button.Content);
 
 
-        testStatefulElement.State.SetState(() => testStatefulElement.State.RemoveText = true);
+        testStatefulElement.State.SetState(() =>
+        {
+            testStatefulElement.State.ReplaceText = true;
+            testStatefulElement.State.ReplaceTextWith = null;
+        });
 
 
         Assert.Null(button.Content); // Old layout is updated and text is removed
         Assert.Null(button.Content); // Old layout is updated and text is removed
 
 
-        testStatefulElement.State.SetState(() => testStatefulElement.State.RemoveText = false);
+        testStatefulElement.State.SetState(() => testStatefulElement.State.ReplaceText = false);
+
+        Assert.NotNull(button.Content); // Old layout is updated and text is added
+        Assert.IsType<TextBlock>(button.Content);
+    }
+
+    [Fact]
+    public void TestStateReplacesChildInTree()
+    {
+        TestStatefulElement testStatefulElement = new TestStatefulElement();
+        testStatefulElement.CreateState();
+        var native = testStatefulElement.BuildNative();
+
+        Assert.IsType<ContentPresenter>(native);
+        Assert.IsType<Avalonia.Controls.Button>((native as ContentPresenter).Content);
+        Avalonia.Controls.Button button = (native as ContentPresenter).Content as Avalonia.Controls.Button;
+
+        Assert.NotNull(button.Content);
+        Assert.IsType<TextBlock>(button.Content);
+
+        testStatefulElement.State.SetState(() =>
+        {
+            testStatefulElement.State.ReplaceText = true;
+            testStatefulElement.State.ReplaceTextWith = new Button();
+        });
+
+        Assert.IsType<Avalonia.Controls.Button>(button.Content); // Old layout is updated and text is removed
+
+        testStatefulElement.State.SetState(() => testStatefulElement.State.ReplaceText = false);
 
 
         Assert.NotNull(button.Content); // Old layout is updated and text is added
         Assert.NotNull(button.Content); // Old layout is updated and text is added
         Assert.IsType<TextBlock>(button.Content);
         Assert.IsType<TextBlock>(button.Content);
@@ -128,6 +164,7 @@ public class LayoutBuilderTests
         Assert.IsType<StackPanel>(panel.Children[1]);
         Assert.IsType<StackPanel>(panel.Children[1]);
 
 
         Assert.Empty((panel.Children[1] as StackPanel).Children);
         Assert.Empty((panel.Children[1] as StackPanel).Children);
+        Assert.Empty(testStatefulElement.State.Rows);
 
 
         Avalonia.Controls.Button button = (Avalonia.Controls.Button)panel.Children[0];
         Avalonia.Controls.Button button = (Avalonia.Controls.Button)panel.Children[0];
         StackPanel innerPanel = (StackPanel)panel.Children[1];
         StackPanel innerPanel = (StackPanel)panel.Children[1];
@@ -135,6 +172,7 @@ public class LayoutBuilderTests
         button.RaiseEvent(new RoutedEventArgs(Avalonia.Controls.Button.ClickEvent));
         button.RaiseEvent(new RoutedEventArgs(Avalonia.Controls.Button.ClickEvent));
 
 
         Assert.Single(innerPanel.Children);
         Assert.Single(innerPanel.Children);
+        Assert.Single(testStatefulElement.State.Rows);
         Assert.IsType<TextBlock>(innerPanel.Children[0]);
         Assert.IsType<TextBlock>(innerPanel.Children[0]);
 
 
         button.RaiseEvent(new RoutedEventArgs(Avalonia.Controls.Button.ClickEvent));
         button.RaiseEvent(new RoutedEventArgs(Avalonia.Controls.Button.ClickEvent));

+ 4 - 1
src/PixiEditor.Extensions.Tests/TestMultiChildState.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.LayoutBuilding.Events;
+using System.Collections;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding.Events;
 using PixiEditor.Extensions.LayoutBuilding.Elements;
 using PixiEditor.Extensions.LayoutBuilding.Elements;
 
 
 namespace PixiEditor.Extensions.Test;
 namespace PixiEditor.Extensions.Test;
@@ -6,6 +7,8 @@ namespace PixiEditor.Extensions.Test;
 public class TestMultiChildState : State
 public class TestMultiChildState : State
 {
 {
     private LayoutElement[] rows = Array.Empty<LayoutElement>();
     private LayoutElement[] rows = Array.Empty<LayoutElement>();
+    public LayoutElement[] Rows => rows;
+
     public override LayoutElement BuildElement()
     public override LayoutElement BuildElement()
     {
     {
         return new Column(
         return new Column(

+ 3 - 2
src/PixiEditor.Extensions.Tests/TestState.cs

@@ -7,13 +7,14 @@ public class TestState : State
 {
 {
     public const string Format = "Clicked: {0}";
     public const string Format = "Clicked: {0}";
     public int ClickedTimes { get; private set; } = 0;
     public int ClickedTimes { get; private set; } = 0;
-    public bool RemoveText { get; set; } = false;
+    public bool ReplaceText { get; set; } = false;
+    public LayoutElement? ReplaceTextWith { get; set; } = null;
 
 
     public override LayoutElement BuildElement()
     public override LayoutElement BuildElement()
     {
     {
         return new Button(
         return new Button(
             onClick: OnClick,
             onClick: OnClick,
-            child: RemoveText ? null : new Text(string.Format(Format, ClickedTimes)));
+            child: ReplaceText ? ReplaceTextWith : new Text(string.Format(Format, ClickedTimes)));
     }
     }
 
 
     private void OnClick(ElementEventArgs args)
     private void OnClick(ElementEventArgs args)

+ 11 - 9
src/PixiEditor.Extensions/LayoutBuilding/Elements/StatefulElement.cs

@@ -1,4 +1,5 @@
-using Avalonia.Controls;
+using System.Collections;
+using Avalonia.Controls;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
@@ -62,6 +63,7 @@ public abstract class StatefulElement<TState> : LayoutElement, IStatefulElement<
             // Replace the entire node if the types are different
             // Replace the entire node if the types are different
             parent?.RemoveChild(oldNode);
             parent?.RemoveChild(oldNode);
             oldNode = newNode;
             oldNode = newNode;
+            parent?.AddChild(oldNode);
             return;
             return;
         }
         }
 
 
@@ -77,24 +79,24 @@ public abstract class StatefulElement<TState> : LayoutElement, IStatefulElement<
                 PerformDiff(oldChildren.Current, newChildren.Current, oldDeserializable);
                 PerformDiff(oldChildren.Current, newChildren.Current, oldDeserializable);
             }
             }
 
 
-            if (oldChildren.Current == null && newChildren.Current != null)
-            {
-                oldDeserializable.AddChild(newChildren.Current);
-            }
-            else if (oldChildren.Current != null && newChildren.Current == null)
+            while (oldChildren.MoveNext())
             {
             {
                 oldDeserializable.RemoveChild(oldChildren.Current);
                 oldDeserializable.RemoveChild(oldChildren.Current);
             }
             }
 
 
-            while (oldChildren.MoveNext())
+            while (newChildren.MoveNext())
             {
             {
-                oldDeserializable.RemoveChild(oldChildren.Current);
+                oldDeserializable.AddChild(newChildren.Current);
             }
             }
 
 
-            while (newChildren.MoveNext())
+            if (oldChildren.Current == null && newChildren.Current != null && oldDeserializable.Count() < newDeserializable.Count())
             {
             {
                 oldDeserializable.AddChild(newChildren.Current);
                 oldDeserializable.AddChild(newChildren.Current);
             }
             }
+            else if (oldChildren.Current != null && newChildren.Current == null && oldDeserializable.Count() > newDeserializable.Count())
+            {
+                oldDeserializable.RemoveChild(oldChildren.Current);
+            }
         }
         }
     }
     }