diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs index 6e21887dea..64fbe20873 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -35,7 +35,7 @@ namespace Perspex.Markup.Binding if (_target != null) { - Subscribe(_target); + SubscribeAndUpdate(_target); } else { @@ -95,7 +95,7 @@ namespace Perspex.Markup.Binding } } - protected abstract void Subscribe(object target); + protected abstract void SubscribeAndUpdate(object target); protected abstract void Unsubscribe(object target); diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index c2ad61bbb4..1c48d272d8 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -18,7 +18,7 @@ namespace Perspex.Markup.Binding public string PropertyName { get; } - protected override void Subscribe(object target) + protected override void SubscribeAndUpdate(object target) { var result = ExpressionValue.None; diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs index 22770868b5..f564788f56 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs @@ -63,66 +63,94 @@ namespace Perspex.Markup.UnitTests.Binding [Fact] public void Should_Track_End_Of_Property_Chain_Changing() { - var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Class2.Bar"); + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x.Value)); - data.Class2.Bar = "baz"; + ((Class2)data.Next).Bar = "baz"; Assert.Equal(new[] { "bar", "baz" }, result); sub.Dispose(); Assert.Equal(0, data.SubscriptionCount); - Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); } [Fact] - public void Should_Track_Middle_Of_Property_Chain_Changing() + public void Should_Track_Property_Chain_Changing() { - var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Class2.Bar"); + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x.Value)); - var old = data.Class2; - data.Class2 = new Class2 { Bar = "baz" }; + var old = data.Next; + data.Next = new Class2 { Bar = "baz" }; Assert.Equal(new[] { "bar", "baz" }, result); sub.Dispose(); Assert.Equal(0, data.SubscriptionCount); - Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); Assert.Equal(0, old.SubscriptionCount); } [Fact] - public void Should_Track_Middle_Of_Property_Chain_Breaking_Then_Mending() + public void Should_Track_Property_Chain_Breaking_With_Null_Then_Mending() { - var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; - var target = new ExpressionObserver(data, "Class2.Bar"); + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); var result = new List(); var sub = target.Subscribe(x => result.Add(x.Value)); - var old = data.Class2; - data.Class2 = null; - data.Class2 = new Class2 { Bar = "baz" }; + var old = data.Next; + data.Next = null; + data.Next = new Class2 { Bar = "baz" }; Assert.Equal(new[] { "bar", null, "baz" }, result); sub.Dispose(); Assert.Equal(0, data.SubscriptionCount); - Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); Assert.Equal(0, old.SubscriptionCount); } + [Fact] + public void Should_Track_Property_Chain_Breaking_With_Object_Then_Mending() + { + var data = new Class1 { Next = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Next.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + var old = data.Next; + var breaking = new WithoutBar(); + data.Next = breaking; + data.Next = new Class2 { Bar = "baz" }; + + Assert.Equal(new[] { "bar", null, "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Next.SubscriptionCount); + Assert.Equal(0, breaking.SubscriptionCount); + Assert.Equal(0, old.SubscriptionCount); + } + + private interface INext + { + int SubscriptionCount { get; } + } + private class Class1 : NotifyingBase { private string _foo; - private Class2 _class2; + private INext _next; public string Foo { @@ -134,18 +162,18 @@ namespace Perspex.Markup.UnitTests.Binding } } - public Class2 Class2 + public INext Next { - get { return _class2; } + get { return _next; } set { - _class2 = value; - RaisePropertyChanged(nameof(Class2)); + _next = value; + RaisePropertyChanged(nameof(Next)); } } } - private class Class2 : NotifyingBase + private class Class2 : NotifyingBase, INext { private string _bar; @@ -159,5 +187,9 @@ namespace Perspex.Markup.UnitTests.Binding } } } + + private class WithoutBar : NotifyingBase, INext + { + } } } diff --git a/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs index ba8cfc57f7..5cc6a32fea 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.ComponentModel; +using System.Linq; namespace Perspex.Markup.UnitTests.Binding { @@ -19,8 +20,11 @@ namespace Perspex.Markup.UnitTests.Binding remove { - _propertyChanged -= value; - --SubscriptionCount; + if (_propertyChanged?.GetInvocationList().Contains(value) == true) + { + _propertyChanged -= value; + --SubscriptionCount; + } } } diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index f1c7abc376..675e280df9 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -74,6 +74,7 @@ +