Browse Source

Add unit tests for RoutedViewHost

artyom 6 years ago
parent
commit
6e4b9a23c2

+ 45 - 14
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@@ -26,9 +26,20 @@ namespace Avalonia
         /// </summary> 
         public static readonly AvaloniaProperty<object> DefaultContentProperty =
             AvaloniaProperty.Register<RoutedViewHost, object>(nameof(DefaultContent));
-    
-        private readonly IAnimation _fadeOutAnimation = CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25));
-        private readonly IAnimation _fadeInAnimation = CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25));
+
+        /// <summary>
+        /// Fade in animation property.
+        /// </summary>
+        public static readonly AvaloniaProperty<IAnimation> FadeInAnimationProperty =
+            AvaloniaProperty.Register<RoutedViewHost, IAnimation>(nameof(DefaultContent),
+                CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25)));
+
+        /// <summary>
+        /// Fade out animation property.
+        /// </summary>
+        public static readonly AvaloniaProperty<IAnimation> FadeOutAnimationProperty =
+            AvaloniaProperty.Register<RoutedViewHost, IAnimation>(nameof(DefaultContent),
+                CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25)));
     
         /// <summary>
         /// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
@@ -39,16 +50,11 @@ namespace Avalonia
             {
                 this.WhenAnyObservable(x => x.Router.CurrentViewModel)
                     .DistinctUntilChanged()
-                    .Subscribe(HandleViewModelChange)
+                    .Subscribe(NavigateToViewModel)
                     .DisposeWith(disposables);
             });
         }
         
-        /// <summary>
-        /// Gets or sets the ReactiveUI view locator used by this router.
-        /// </summary>
-        public IViewLocator ViewLocator { get; set; }
-    
         /// <summary>
         /// Gets or sets the <see cref="RoutingState"/> of the view model stack.
         /// </summary>
@@ -66,15 +72,38 @@ namespace Avalonia
             get => GetValue(DefaultContentProperty);
             set => SetValue(DefaultContentProperty, value);
         }
+
+        /// <summary>
+        /// Gets or sets the animation played when page appears.
+        /// </summary>
+        public IAnimation FadeInAnimation
+        {
+            get => GetValue(FadeInAnimationProperty);
+            set => SetValue(FadeInAnimationProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the animation played when page disappears.
+        /// </summary>
+        public IAnimation FadeOutAnimation
+        {
+            get => GetValue(FadeOutAnimationProperty);
+            set => SetValue(FadeOutAnimationProperty, value);
+        }
     
         /// <summary>
         /// Duplicates the Content property with a private setter.
         /// </summary>
         public new object Content
         {
-            get => base.Content;
+            get => base.Content ?? DefaultContent;
             private set => base.Content = value;
         }
+
+        /// <summary>
+        /// Gets or sets the ReactiveUI view locator used by this router.
+        /// </summary>
+        public IViewLocator ViewLocator { get; set; }
     
         /// <summary>
         /// Invoked when ReactiveUI router navigates to a view model.
@@ -83,12 +112,12 @@ namespace Avalonia
         /// <exception cref="Exception">
         /// Thrown when ViewLocator is unable to find the appropriate view.
         /// </exception>
-        private void HandleViewModelChange(IRoutableViewModel viewModel)
+        private void NavigateToViewModel(IRoutableViewModel viewModel)
         {
             if (viewModel == null)
             {
                 this.Log().Info("ViewModel is null, falling back to default content.");
-                UpdateContent(DefaultContent);
+                UpdateContent(null);
                 return;
             }
     
@@ -107,9 +136,11 @@ namespace Avalonia
         /// <param name="newContent">New content to set.</param>
         private async void UpdateContent(object newContent)
         {
-            await _fadeOutAnimation.RunAsync(this, null);
+            if (FadeOutAnimation != null)
+                await FadeOutAnimation.RunAsync(this, Clock);
             Content = newContent;
-            await _fadeInAnimation.RunAsync(this, null);
+            if (FadeInAnimation != null)
+                await FadeInAnimation.RunAsync(this, Clock);
         }
     
         /// <summary>

+ 104 - 0
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@@ -0,0 +1,104 @@
+using System;
+using System.Reactive.Concurrency;
+using System.Reactive.Disposables;
+using Avalonia.Controls;
+using Avalonia.Rendering;
+using Avalonia.Platform;
+using Avalonia.UnitTests;
+using Avalonia;
+using ReactiveUI;
+using DynamicData;
+using Xunit;
+using Splat;
+using Avalonia.Markup.Xaml;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Reactive;
+
+namespace Avalonia
+{
+    public class RoutedViewHostTest
+    {
+        public class FirstRoutableViewModel : ReactiveObject, IRoutableViewModel
+        {
+            public string UrlPathSegment => "first";
+
+            public IScreen HostScreen { get; set; }
+        }
+
+        public class FirstRoutableView : ReactiveUserControl<FirstRoutableViewModel> { }
+
+        public class SecondRoutableViewModel : ReactiveObject, IRoutableViewModel
+        {
+            public string UrlPathSegment => "second";
+
+            public IScreen HostScreen { get; set; }
+        }
+
+        public class SecondRoutableView : ReactiveUserControl<SecondRoutableViewModel> { }
+
+        public class ScreenViewModel : ReactiveObject, IScreen
+        {
+            public RoutingState Router { get; } = new RoutingState();
+        }
+
+        public RoutedViewHostTest()
+        {
+            Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
+            Locator.CurrentMutable.Register(() => new FirstRoutableView(), typeof(IViewFor<FirstRoutableViewModel>));
+            Locator.CurrentMutable.Register(() => new SecondRoutableView(), typeof(IViewFor<SecondRoutableViewModel>));
+        }
+
+        [Fact]
+        public void RoutedViewHostShouldStayInSyncWithRoutingState() 
+        {
+            var screen = new ScreenViewModel();
+            var defaultContent = new TextBlock();
+            var host = new RoutedViewHost 
+            { 
+                Router = screen.Router,
+                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);
+
+            screen.Router.Navigate
+                .Execute(new FirstRoutableViewModel())
+                .Subscribe();
+
+            Assert.NotNull(host.Content);
+            Assert.Equal(typeof(FirstRoutableView), host.Content.GetType());
+
+            screen.Router.Navigate
+                .Execute(new SecondRoutableViewModel())
+                .Subscribe();
+
+            Assert.NotNull(host.Content);
+            Assert.Equal(typeof(SecondRoutableView), host.Content.GetType());
+
+            screen.Router.NavigateBack
+                .Execute(Unit.Default)
+                .Subscribe();
+
+            Assert.NotNull(host.Content);
+            Assert.Equal(typeof(FirstRoutableView), host.Content.GetType());
+
+            screen.Router.NavigateBack
+                .Execute(Unit.Default)
+                .Subscribe();
+
+            Assert.NotNull(host.Content);
+            Assert.Equal(typeof(TextBlock), host.Content.GetType());
+            Assert.Equal(defaultContent, host.Content);
+        }
+    }
+}