浏览代码

another simple unit tests for issue #855 for direct and styled properties

donandren 8 年之前
父节点
当前提交
ef81c55960

+ 4 - 0
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@@ -124,6 +124,10 @@
       <Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
       <Name>Avalonia.Base</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
+      <Project>{3E53A01A-B331-47F3-B828-4A5717E77A24}</Project>
+      <Name>Avalonia.Markup.Xaml</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj">
       <Project>{88060192-33d5-4932-b0f9-8bd2763e857d}</Project>
       <Name>Avalonia.UnitTests</Name>

+ 91 - 12
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@@ -2,22 +2,21 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reactive.Concurrency;
 using System.Reactive.Linq;
 using System.Reactive.Subjects;
-using Microsoft.Reactive.Testing;
+using System.Threading;
+using System.Threading.Tasks;
 using Avalonia.Data;
 using Avalonia.Logging;
-using Avalonia.UnitTests;
-using Xunit;
-using System.Threading.Tasks;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.Platform;
-using System.Threading;
-using Moq;
-using System.Reactive.Disposables;
-using System.Reactive.Concurrency;
 using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Microsoft.Reactive.Testing;
+using Moq;
+using Xunit;
 
 namespace Avalonia.Base.UnitTests
 {
@@ -363,7 +362,7 @@ namespace Avalonia.Base.UnitTests
                 Assert.True(called);
             }
         }
-        
+
         [Fact]
         public async void Bind_With_Scheduler_Executes_On_Scheduler()
         {
@@ -384,7 +383,43 @@ namespace Avalonia.Base.UnitTests
 
                 await Task.Run(() => source.OnNext(6.7));
             }
+        }
+
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new Class1();
+
+            //note: if the initialization of the child binding is here target/child binding work fine!!!
+            //var child = new Class1()
+            //{
+            //    [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty]
+            //};
 
+            target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+            var child = new Class1()
+            {
+                [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty]
+            };
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            //here in real life stack overflow exception is thrown issue #855 and #824
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
         }
 
         /// <summary>
@@ -405,6 +440,15 @@ namespace Avalonia.Base.UnitTests
 
             public static readonly StyledProperty<double> QuxProperty =
                 AvaloniaProperty.Register<Class1, double>("Qux", 5.6);
+
+            public static readonly StyledProperty<double> DoubleValueProperty =
+                        AvaloniaProperty.Register<Class1, double>(nameof(DoubleValue));
+
+            public double DoubleValue
+            {
+                get { return GetValue(DoubleValueProperty); }
+                set { SetValue(DoubleValueProperty, value); }
+            }
         }
 
         private class Class2 : Class1
@@ -431,5 +475,40 @@ namespace Avalonia.Base.UnitTests
                 return new InstancedBinding(_source, BindingMode.OneTime);
             }
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
-}
+}

+ 96 - 10
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@@ -3,10 +3,11 @@
 
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Reactive.Subjects;
-using Avalonia;
 using Avalonia.Data;
 using Avalonia.Logging;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -208,7 +209,7 @@ namespace Avalonia.Base.UnitTests
         {
             var target = new Class1();
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.SetValue(Class1.BarProperty, "newvalue"));
         }
 
@@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests
         {
             var target = new Class1();
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
         }
 
@@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests
             var target = new Class1();
             var source = new Subject<string>();
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.Bind(Class1.BarProperty, source));
         }
 
@@ -439,12 +440,49 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
         }
 
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new Class1();
+
+            //note: if the initialization of the child binding is here there is no stackoverflow!!!
+            //var child = new Class1()
+            //{
+            //    [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty]
+            //};
+
+            target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+            var child = new Class1()
+            {
+                [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty]
+            };
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            //here in real life stack overflow exception is thrown issue #855 and #824
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly DirectProperty<Class1, string> FooProperty =
                 AvaloniaProperty.RegisterDirect<Class1, string>(
-                    "Foo", 
-                    o => o.Foo, 
+                    "Foo",
+                    o => o.Foo,
                     (o, v) => o.Foo = v,
                     unsetValue: "unset");
 
@@ -453,14 +491,21 @@ namespace Avalonia.Base.UnitTests
 
             public static readonly DirectProperty<Class1, int> BazProperty =
                 AvaloniaProperty.RegisterDirect<Class1, int>(
-                    "Bar", 
-                    o => o.Baz, 
-                    (o,v) => o.Baz = v,
+                    "Bar",
+                    o => o.Baz,
+                    (o, v) => o.Baz = v,
                     unsetValue: -1);
 
+            public static readonly DirectProperty<Class1, double> DoubleValueProperty =
+                AvaloniaProperty.RegisterDirect<Class1, double>(
+                    nameof(DoubleValue),
+                    o => o.DoubleValue,
+                    (o, v) => o.DoubleValue = v);
+
             private string _foo = "initial";
             private readonly string _bar = "bar";
             private int _baz = 5;
+            private double _doubleValue;
 
             public string Foo
             {
@@ -478,6 +523,12 @@ namespace Avalonia.Base.UnitTests
                 get { return _baz; }
                 set { SetAndRaise(BazProperty, ref _baz, value); }
             }
+
+            public double DoubleValue
+            {
+                get { return _doubleValue; }
+                set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
+            }
         }
 
         private class Class2 : AvaloniaObject
@@ -497,5 +548,40 @@ namespace Avalonia.Base.UnitTests
                 set { SetAndRaise(FooProperty, ref _foo, value); }
             }
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
-}
+}