Browse Source

Merge pull request #8738 from AvaloniaUI/update-and-fix-reactiveui

Update and fix ReactiveUI on master branch + ios
pull/8757/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
f69619c8dc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ReactiveUI.props
  2. 6
      src/Avalonia.Controls/Control.cs
  3. 28
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  4. 4
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  5. 6
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  6. 71
      tests/Avalonia.Controls.UnitTests/LoadedTests.cs
  7. 14
      tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
  8. 11
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  9. 5
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
  10. 5
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
  11. 4
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  12. 3
      tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="13.2.10" />
<PackageReference Include="ReactiveUI" Version="18.3.1" />
</ItemGroup>
</Project>

6
src/Avalonia.Controls/Control.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
@ -104,7 +105,6 @@ namespace Avalonia.Controls
private static readonly HashSet<Control> _loadedQueue = new HashSet<Control>();
private static readonly HashSet<Control> _loadedProcessingQueue = new HashSet<Control>();
private bool _isAttachedToVisualTree = false;
private bool _isLoaded = false;
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
@ -347,7 +347,7 @@ namespace Avalonia.Controls
internal void OnLoadedCore()
{
if (_isLoaded == false &&
_isAttachedToVisualTree)
((ILogical)this).IsAttachedToLogicalTree)
{
_isLoaded = true;
OnLoaded();
@ -395,7 +395,6 @@ namespace Avalonia.Controls
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
_isAttachedToVisualTree = true;
InitializeIfNeeded();
@ -406,7 +405,6 @@ namespace Avalonia.Controls
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
_isAttachedToVisualTree = false;
OnUnloadedCore();
}

28
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -2,6 +2,7 @@ using System;
using System.Reactive.Linq;
using Avalonia.VisualTree;
using Avalonia.Controls;
using Avalonia.Interactivity;
using ReactiveUI;
namespace Avalonia.ReactiveUI
@ -25,27 +26,28 @@ namespace Avalonia.ReactiveUI
public IObservable<bool> GetActivationForView(IActivatableView view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
if (view is WindowBase window) return GetActivationForWindowBase(window);
if (view is Control control) return GetActivationForControl(control);
return GetActivationForVisual(visual);
}
/// <summary>
/// Listens to Opened and Closed events for Avalonia windows.
/// Listens to Loaded and Unloaded
/// events for Avalonia Control.
/// </summary>
private IObservable<bool> GetActivationForWindowBase(WindowBase window)
private IObservable<bool> GetActivationForControl(Control control)
{
var windowLoaded = Observable
.FromEventPattern(
x => window.Opened += x,
x => window.Opened -= x)
var controlLoaded = Observable
.FromEventPattern<RoutedEventArgs>(
x => control.Loaded += x,
x => control.Loaded -= x)
.Select(args => true);
var windowUnloaded = Observable
.FromEventPattern(
x => window.Closed += x,
x => window.Closed -= x)
var controlUnloaded = Observable
.FromEventPattern<RoutedEventArgs>(
x => control.Unloaded += x,
x => control.Unloaded -= x)
.Select(args => false);
return windowLoaded
.Merge(windowUnloaded)
return controlLoaded
.Merge(controlUnloaded)
.DistinctUntilChanged();
}

4
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -50,7 +50,7 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger, IStyleable
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.
@ -126,6 +126,8 @@ namespace Avalonia.ReactiveUI
/// </summary>
public IViewLocator? ViewLocator { get; set; }
Type IStyleable.StyleKey => typeof(TransitioningContentControl);
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>

6
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -2,7 +2,7 @@ using System;
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Styling;
using ReactiveUI;
using Splat;
@ -13,7 +13,7 @@ namespace Avalonia.ReactiveUI
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
/// </summary>
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IStyleable
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
@ -78,6 +78,8 @@ namespace Avalonia.ReactiveUI
/// </summary>
public IViewLocator? ViewLocator { get; set; }
Type IStyleable.StyleKey => typeof(TransitioningContentControl);
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>

71
tests/Avalonia.Controls.UnitTests/LoadedTests.cs

@ -0,0 +1,71 @@
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests;
public class LoadedTests
{
[Fact]
public void Window_Loads_And_Unloads()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
int loadedCount = 0, unloadedCount = 0;
var target = new Window();
target.Loaded += (_, _) => loadedCount++;
target.Unloaded += (_, _) => unloadedCount++;
Assert.Equal(0, loadedCount);
Assert.Equal(0, unloadedCount);
target.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(target.IsLoaded);
Assert.Equal(1, loadedCount);
Assert.Equal(0, unloadedCount);
target.Close();
Assert.Equal(1, loadedCount);
Assert.Equal(1, unloadedCount);
Assert.False(target.IsLoaded);
}
}
[Fact]
public void Control_Loads_And_Unloads()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
int loadedCount = 0, unloadedCount = 0;
var window = new Window();
window.Show();
var target = new Button();
target.Loaded += (_, _) => loadedCount++;
target.Unloaded += (_, _) => unloadedCount++;
Assert.Equal(0, loadedCount);
Assert.Equal(0, unloadedCount);
window.Content = target;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(target.IsLoaded);
Assert.Equal(1, loadedCount);
Assert.Equal(0, unloadedCount);
window.Content = null;
Assert.Equal(1, loadedCount);
Assert.Equal(1, unloadedCount);
Assert.False(target.IsLoaded);
}
}
}

14
tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs

@ -7,6 +7,7 @@ using System.Reactive;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using Avalonia.Controls.ApplicationLifetimes;
@ -17,6 +18,7 @@ using Avalonia.UnitTests;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia;
using Avalonia.Threading;
using ReactiveUI;
using DynamicData;
using Xunit;
@ -93,13 +95,23 @@ namespace Avalonia.ReactiveUI.UnitTests
var suspension = new AutoSuspendHelper(application.ApplicationLifetime);
RxApp.SuspensionHost.CreateNewAppState = () => new AppState { Example = "Foo" };
RxApp.SuspensionHost.ShouldPersistState.Subscribe(_ => shouldPersistReceived = true);
RxApp.SuspensionHost.SetupDefaultSuspendResume(new DummySuspensionDriver());
RxApp.SuspensionHost.SetupDefaultSuspendResume(new FakeSuspensionDriver());
suspension.OnFrameworkInitializationCompleted();
lifetime.Shutdown();
Assert.True(shouldPersistReceived);
Assert.Equal("Foo", RxApp.SuspensionHost.GetAppState<AppState>().Example);
}
}
private class FakeSuspensionDriver : ISuspensionDriver
{
public IObservable<object> LoadState() => Observable.Empty<object>();
public IObservable<Unit> SaveState(object state) => Observable.Empty<Unit>();
public IObservable<Unit> InvalidateState() => Observable.Empty<Unit>();
}
}
}

11
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -12,6 +12,7 @@ using Xunit;
using Splat;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Avalonia.ReactiveUI.UnitTests
{
@ -109,10 +110,12 @@ namespace Avalonia.ReactiveUI.UnitTests
var fakeRenderedDecorator = new TestRoot();
fakeRenderedDecorator.Child = userControl;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(activated[0]);
Assert.Equal(1, activated.Count);
fakeRenderedDecorator.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(activated[0]);
Assert.False(activated[1]);
Assert.Equal(2, activated.Count);
@ -139,9 +142,11 @@ namespace Avalonia.ReactiveUI.UnitTests
var fakeRenderedDecorator = new TestRoot();
fakeRenderedDecorator.Child = userControl;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(userControl.Active);
fakeRenderedDecorator.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(userControl.Active);
}
@ -154,9 +159,11 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.False(window.Active);
window.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(window.Active);
window.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(window.Active);
}
}
@ -171,9 +178,11 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.False(viewModel.IsActivated);
window.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(viewModel.IsActivated);
window.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(viewModel.IsActivated);
}
}
@ -187,9 +196,11 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.False(viewModel.IsActivated);
root.Child = control;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.True(viewModel.IsActivated);
root.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.False(viewModel.IsActivated);
}
}

5
tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs

@ -1,5 +1,6 @@
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@ -69,12 +70,14 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.False(view.ViewModel.IsActive);
root.Child = view;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
root.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
@ -90,12 +93,14 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.False(view.ViewModel.IsActive);
root.Child = view;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
root.Child = null;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);

5
tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs

@ -1,4 +1,5 @@
using System.Reactive.Disposables;
using Avalonia.Threading;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@ -72,12 +73,14 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.False(view.ViewModel.IsActive);
view.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
view.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
@ -96,12 +99,14 @@ namespace Avalonia.ReactiveUI.UnitTests
view.Show();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
view.Close();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);

4
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@ -15,6 +15,7 @@ using System.ComponentModel;
using System.Threading.Tasks;
using System.Reactive;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Avalonia.ReactiveUI.UnitTests
{
@ -75,6 +76,7 @@ namespace Avalonia.ReactiveUI.UnitTests
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
@ -126,6 +128,7 @@ namespace Avalonia.ReactiveUI.UnitTests
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
@ -191,6 +194,7 @@ namespace Avalonia.ReactiveUI.UnitTests
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.Equal(defaultContent, host.Content);

3
tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@ -46,6 +47,7 @@ namespace Avalonia.ReactiveUI.UnitTests
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
@ -91,6 +93,7 @@ namespace Avalonia.ReactiveUI.UnitTests
Child = host
};
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);

Loading…
Cancel
Save