diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index bb966a08d5..5a3ba93f44 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -3,11 +3,10 @@ using System; using System.ComponentModel; -using System.Linq; using System.Reactive.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; -using Perspex.Threading; namespace Perspex.Markup.Binding { @@ -93,6 +92,7 @@ namespace Perspex.Markup.Binding { CurrentValue = ExpressionValue.None; _subscription = observable + .ObserveOn(SynchronizationContext.Current) .Subscribe(x => CurrentValue = new ExpressionValue(x)); } else if (task != null) @@ -101,7 +101,9 @@ namespace Perspex.Markup.Binding if (resultProperty != null) { - task.ContinueWith(x => CurrentValue = new ExpressionValue(resultProperty.GetValue(task))) + task.ContinueWith( + x => CurrentValue = new ExpressionValue(resultProperty.GetValue(task)), + TaskScheduler.FromCurrentSynchronizationContext()) .ConfigureAwait(false); } } diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs index 1bdba260b6..979fa3856c 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs @@ -15,32 +15,39 @@ namespace Perspex.Markup.UnitTests.Binding [Fact] public void Should_Get_Simple_Observable_Value() { - var source = new BehaviorSubject("foo"); - var data = new { Foo = source }; - var target = new ExpressionObserver(data, "Foo"); - var result = new List(); + using (var sync = UnitTestSynchronizationContext.Begin()) + { + var source = new BehaviorSubject("foo"); + var data = new { Foo = source }; + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); - var sub = target.Subscribe(x => result.Add(x.Value)); - source.OnNext("bar"); + var sub = target.Subscribe(x => result.Add(x.Value)); + source.OnNext("bar"); + sync.ExecutePostedCallbacks(); - Assert.Equal(new[] { "foo", "bar" }, result); + Assert.Equal(new[] { null, "foo", "bar" }, result); + } } [Fact] public void Should_Get_Property_Value_From_Observable() { - var data = new Class1(); - var target = new ExpressionObserver(data, "Next.Foo"); - var result = new List(); - - var sub = target.Subscribe(x => result.Add(x.Value)); - data.Next.OnNext(new Class2("foo")); + using (var sync = UnitTestSynchronizationContext.Begin()) + { + var data = new Class1(); + var target = new ExpressionObserver(data, "Next.Foo"); + var result = new List(); - Assert.Equal(new[] { null, "foo" }, result); + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Next.OnNext(new Class2("foo")); + sync.ExecutePostedCallbacks(); - sub.Dispose(); + Assert.Equal(new[] { null, "foo" }, result); - Assert.Equal(0, data.SubscriptionCount); + sub.Dispose(); + Assert.Equal(0, data.SubscriptionCount); + } } private class Class1 : NotifyingBase diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs index 1854c3b858..9cb0cd4454 100644 --- a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; using Perspex.Markup.Binding; using Xunit; @@ -15,29 +16,37 @@ namespace Perspex.Markup.UnitTests.Binding [Fact] public void Should_Get_Simple_Task_Value() { - var tcs = new TaskCompletionSource(); - var data = new { Foo = tcs.Task }; - var target = new ExpressionObserver(data, "Foo"); - var result = new List(); + using (var sync = UnitTestSynchronizationContext.Begin()) + { + var tcs = new TaskCompletionSource(); + var data = new { Foo = tcs.Task }; + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); - var sub = target.Subscribe(x => result.Add(x.Value)); - tcs.SetResult("foo"); + var sub = target.Subscribe(x => result.Add(x.Value)); + tcs.SetResult("foo"); + sync.ExecutePostedCallbacks(); - Assert.Equal(new object[] { null, "foo" }, result.ToArray()); + Assert.Equal(new object[] { null, "foo" }, result.ToArray()); + } } [Fact] public void Should_Get_Property_Value_From_Task() { - var tcs = new TaskCompletionSource(); - var data = new Class1(tcs.Task); - var target = new ExpressionObserver(data, "Next.Foo"); - var result = new List(); + using (var sync = UnitTestSynchronizationContext.Begin()) + { + var tcs = new TaskCompletionSource(); + var data = new Class1(tcs.Task); + var target = new ExpressionObserver(data, "Next.Foo"); + var result = new List(); - var sub = target.Subscribe(x => result.Add(x.Value)); - tcs.SetResult(new Class2("foo")); + var sub = target.Subscribe(x => result.Add(x.Value)); + tcs.SetResult(new Class2("foo")); + sync.ExecutePostedCallbacks(); - Assert.Equal(new object[] { null, "foo" }, result.ToArray()); + Assert.Equal(new object[] { null, "foo" }, result.ToArray()); + } } private class Class1 : NotifyingBase diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index ecc0dd5ef7..5aee71357c 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -81,6 +81,7 @@ + diff --git a/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs b/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs new file mode 100644 index 0000000000..1a1eaf4ecf --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs @@ -0,0 +1,68 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Threading; + +namespace Perspex.Markup.UnitTests +{ + internal sealed class UnitTestSynchronizationContext : SynchronizationContext + { + readonly List> _postedCallbacks = + new List>(); + + public static Scope Begin() + { + var sync = new UnitTestSynchronizationContext(); + var old = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(sync); + return new Scope(old, sync); + } + + public override void Send(SendOrPostCallback d, object state) + { + d(state); + } + + public override void Post(SendOrPostCallback d, object state) + { + lock (_postedCallbacks) + { + _postedCallbacks.Add(Tuple.Create(d, state)); + } + } + + public void ExecutePostedCallbacks() + { + lock (_postedCallbacks) + { + _postedCallbacks.ForEach(t => t.Item1(t.Item2)); + _postedCallbacks.Clear(); + } + } + + public class Scope : IDisposable + { + private SynchronizationContext _old; + private UnitTestSynchronizationContext _new; + + public Scope(SynchronizationContext old, UnitTestSynchronizationContext n) + { + _old = old; + _new = n; + } + + public void Dispose() + { + SynchronizationContext.SetSynchronizationContext(_old); + } + + public void ExecutePostedCallbacks() + { + _new.ExecutePostedCallbacks(); + } + } + } +}