diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
index 07ed7f14ca..c712a8bcf9 100644
--- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
+++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
@@ -124,6 +124,10 @@
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}
Avalonia.Base
+
+ {3E53A01A-B331-47F3-B828-4A5717E77A24}
+ Avalonia.Markup.Xaml
+
{88060192-33d5-4932-b0f9-8bd2763e857d}
Avalonia.UnitTests
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index 5e286305d2..7186207b92 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/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);
}
///
@@ -405,6 +440,15 @@ namespace Avalonia.Base.UnitTests
public static readonly StyledProperty QuxProperty =
AvaloniaProperty.Register("Qux", 5.6);
+
+ public static readonly StyledProperty DoubleValueProperty =
+ AvaloniaProperty.Register(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)));
+ }
+ }
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
index ecb555252d..b6e5396020 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
+++ b/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(() =>
+ Assert.Throws(() =>
target.SetValue(Class1.BarProperty, "newvalue"));
}
@@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests
{
var target = new Class1();
- Assert.Throws(() =>
+ Assert.Throws(() =>
target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
}
@@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1();
var source = new Subject();
- Assert.Throws(() =>
+ Assert.Throws(() =>
target.Bind(Class1.BarProperty, source));
}
@@ -439,12 +440,49 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().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 FooProperty =
AvaloniaProperty.RegisterDirect(
- "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 BazProperty =
AvaloniaProperty.RegisterDirect(
- "Bar",
- o => o.Baz,
- (o,v) => o.Baz = v,
+ "Bar",
+ o => o.Baz,
+ (o, v) => o.Baz = v,
unsetValue: -1);
+ public static readonly DirectProperty DoubleValueProperty =
+ AvaloniaProperty.RegisterDirect(
+ 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)));
+ }
+ }
+ }
+ }
}
-}
+}
\ No newline at end of file