diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index 84108a9b52..6f80bff4b8 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia 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.Reactive.Disposables; using ReactiveUI; using Splat; @@ -18,7 +20,20 @@ namespace Avalonia.ReactiveUI /// public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty.Register(nameof(ViewModel)); - + + /// + /// Initializes a new instance of the class. + /// + public ViewModelViewHost() + { + this.WhenActivated(disposables => + { + this.WhenAnyValue(x => x.ViewModel) + .Subscribe(NavigateToViewModel) + .DisposeWith(disposables); + }); + } + /// /// Gets or sets the ViewModel to display. /// @@ -33,16 +48,6 @@ namespace Avalonia.ReactiveUI /// public IViewLocator ViewLocator { get; set; } - /// - /// Updates the Content when ViewModel changes. - /// - /// Property changed event arguments. - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - if (e.Property.Name == nameof(ViewModel)) NavigateToViewModel(e.NewValue); - base.OnPropertyChanged(e); - } - /// /// Invoked when ReactiveUI router navigates to a view model. /// diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs new file mode 100644 index 0000000000..79e58f63e9 --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs @@ -0,0 +1,6 @@ +using Xunit; + +// Required to avoid InvalidOperationException sometimes thrown +// from Splat.MemoizingMRUCache.cs which is not thread-safe. +// Thrown when trying to access WhenActivated concurrently. +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index d9f1ce47dd..f4dffdc0c3 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -13,7 +13,7 @@ using Splat; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Avalonia +namespace Avalonia.ReactiveUI.UnitTests { public class AvaloniaActivationForViewFetcherTest { diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index 401d169896..c6017d3f5f 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; using System.Reactive; using Avalonia.ReactiveUI; -namespace Avalonia +namespace Avalonia.ReactiveUI.UnitTests { public class RoutedViewHostTest { diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs new file mode 100644 index 0000000000..5d5d15358e --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs @@ -0,0 +1,72 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using ReactiveUI; +using Splat; +using Xunit; + +namespace Avalonia.ReactiveUI.UnitTests +{ + public class ViewModelViewHostTest + { + public class FirstViewModel { } + + public class FirstView : ReactiveUserControl { } + + public class SecondViewModel : ReactiveObject { } + + public class SecondView : ReactiveUserControl { } + + public ViewModelViewHostTest() + { + Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); + Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor)); + } + + [Fact] + public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel() + { + var defaultContent = new TextBlock(); + var host = new ViewModelViewHost + { + DefaultContent = defaultContent, + FadeOutAnimation = null, + FadeInAnimation = null + }; + + var root = new TestRoot + { + Child = host + }; + + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + + var first = new FirstViewModel(); + host.ViewModel = first; + Assert.NotNull(host.Content); + Assert.Equal(typeof(FirstView), host.Content.GetType()); + Assert.Equal(first, ((FirstView)host.Content).DataContext); + Assert.Equal(first, ((FirstView)host.Content).ViewModel); + + var second = new SecondViewModel(); + host.ViewModel = second; + Assert.NotNull(host.Content); + Assert.Equal(typeof(SecondView), host.Content.GetType()); + Assert.Equal(second, ((SecondView)host.Content).DataContext); + Assert.Equal(second, ((SecondView)host.Content).ViewModel); + + host.ViewModel = null; + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + + host.ViewModel = first; + Assert.NotNull(host.Content); + Assert.Equal(typeof(FirstView), host.Content.GetType()); + Assert.Equal(first, ((FirstView)host.Content).DataContext); + Assert.Equal(first, ((FirstView)host.Content).ViewModel); + } + } +} \ No newline at end of file