committed by
GitHub
16 changed files with 786 additions and 240 deletions
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.Markup.Xaml.Templates; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls
|
||||
|
/// that don't have DataTemplates, and assigns a default DataTemplate that
|
||||
|
/// loads the View associated with each ViewModel.
|
||||
|
/// </summary>
|
||||
|
public class AutoDataTemplateBindingHook : IPropertyBindingHook |
||||
|
{ |
||||
|
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>(x => |
||||
|
{ |
||||
|
var control = new ViewModelViewHost(); |
||||
|
var context = control.GetObservable(Control.DataContextProperty); |
||||
|
control.Bind(ViewModelViewHost.ViewModelProperty, context); |
||||
|
control.HorizontalContentAlignment = HorizontalAlignment.Stretch; |
||||
|
control.VerticalContentAlignment = VerticalAlignment.Stretch; |
||||
|
return control; |
||||
|
}, |
||||
|
true); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool ExecuteHook( |
||||
|
object source, object target, |
||||
|
Func<IObservedChange<object, object>[]> getCurrentViewModelProperties, |
||||
|
Func<IObservedChange<object, object>[]> getCurrentViewProperties, |
||||
|
BindingDirection direction) |
||||
|
{ |
||||
|
var viewProperties = getCurrentViewProperties(); |
||||
|
var lastViewProperty = viewProperties.LastOrDefault(); |
||||
|
var itemsControl = lastViewProperty?.Sender as ItemsControl; |
||||
|
if (itemsControl == null) |
||||
|
return true; |
||||
|
|
||||
|
var propertyName = viewProperties.Last().GetPropertyName(); |
||||
|
if (propertyName != "Items" && |
||||
|
propertyName != "ItemsSource") |
||||
|
return true; |
||||
|
|
||||
|
if (itemsControl.ItemTemplate != null) |
||||
|
return true; |
||||
|
|
||||
|
itemsControl.ItemTemplate = DefaultItemTemplate; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
// 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 Avalonia.Animation; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A ContentControl that animates the transition when its content is changed.
|
||||
|
/// </summary>
|
||||
|
public class TransitioningContentControl : ContentControl, IStyleable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// <see cref="AvaloniaProperty"/> for the <see cref="PageTransition"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<IPageTransition> PageTransitionProperty = |
||||
|
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition>(nameof(PageTransition), |
||||
|
new CrossFade(TimeSpan.FromSeconds(0.5))); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<object> DefaultContentProperty = |
||||
|
AvaloniaProperty.Register<TransitioningContentControl, object>(nameof(DefaultContent)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the animation played when content appears and disappears.
|
||||
|
/// </summary>
|
||||
|
public IPageTransition PageTransition |
||||
|
{ |
||||
|
get => GetValue(PageTransitionProperty); |
||||
|
set => SetValue(PageTransitionProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the content displayed whenever there is no page currently routed.
|
||||
|
/// </summary>
|
||||
|
public object DefaultContent |
||||
|
{ |
||||
|
get => GetValue(DefaultContentProperty); |
||||
|
set => SetValue(DefaultContentProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the content with animation.
|
||||
|
/// </summary>
|
||||
|
public new object Content |
||||
|
{ |
||||
|
get => base.Content; |
||||
|
set => UpdateContentWithTransition(value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// TransitioningContentControl uses the default ContentControl
|
||||
|
/// template from Avalonia default theme.
|
||||
|
/// </summary>
|
||||
|
Type IStyleable.StyleKey => typeof(ContentControl); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Updates the content with transitions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="content">New content to set.</param>
|
||||
|
private async void UpdateContentWithTransition(object content) |
||||
|
{ |
||||
|
if (PageTransition != null) |
||||
|
await PageTransition.Start(this, null, true); |
||||
|
base.Content = content; |
||||
|
if (PageTransition != null) |
||||
|
await PageTransition.Start(null, this, true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This content control will automatically load the View associated with
|
||||
|
/// 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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<object> ViewModelProperty = |
||||
|
AvaloniaProperty.Register<ViewModelViewHost, object>(nameof(ViewModel)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
|
||||
|
/// </summary>
|
||||
|
public ViewModelViewHost() |
||||
|
{ |
||||
|
this.WhenActivated(disposables => |
||||
|
{ |
||||
|
this.WhenAnyValue(x => x.ViewModel) |
||||
|
.Subscribe(NavigateToViewModel) |
||||
|
.DisposeWith(disposables); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the ViewModel to display.
|
||||
|
/// </summary>
|
||||
|
public object ViewModel |
||||
|
{ |
||||
|
get => GetValue(ViewModelProperty); |
||||
|
set => SetValue(ViewModelProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the view locator.
|
||||
|
/// </summary>
|
||||
|
public IViewLocator ViewLocator { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invoked when ReactiveUI router navigates to a view model.
|
||||
|
/// </summary>
|
||||
|
/// <param name="viewModel">ViewModel to which the user navigates.</param>
|
||||
|
private void NavigateToViewModel(object viewModel) |
||||
|
{ |
||||
|
if (viewModel == null) |
||||
|
{ |
||||
|
this.Log().Info("ViewModel is null. Falling back to default content."); |
||||
|
Content = DefaultContent; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; |
||||
|
var viewInstance = viewLocator.ResolveView(viewModel); |
||||
|
if (viewInstance == null) |
||||
|
{ |
||||
|
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); |
||||
|
Content = DefaultContent; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); |
||||
|
viewInstance.ViewModel = viewModel; |
||||
|
if (viewInstance is IStyledElement styled) |
||||
|
styled.DataContext = viewModel; |
||||
|
Content = viewInstance; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// 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 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)] |
||||
@ -0,0 +1,116 @@ |
|||||
|
// 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 Xunit; |
||||
|
using ReactiveUI; |
||||
|
using Avalonia.ReactiveUI; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Linq; |
||||
|
using Avalonia.VisualTree; |
||||
|
using Avalonia.Controls.Presenters; |
||||
|
using Splat; |
||||
|
using System.Threading.Tasks; |
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI.UnitTests |
||||
|
{ |
||||
|
public class AutoDataTemplateBindingHookTest |
||||
|
{ |
||||
|
public class NestedViewModel : ReactiveObject { } |
||||
|
|
||||
|
public class NestedView : ReactiveUserControl<NestedViewModel> { } |
||||
|
|
||||
|
public class ExampleViewModel : ReactiveObject |
||||
|
{ |
||||
|
public ObservableCollection<NestedViewModel> Items { get; } = new ObservableCollection<NestedViewModel>(); |
||||
|
} |
||||
|
|
||||
|
public class ExampleView : ReactiveUserControl<ExampleViewModel> |
||||
|
{ |
||||
|
public ItemsControl List { get; } = new ItemsControl(); |
||||
|
|
||||
|
public ExampleView() |
||||
|
{ |
||||
|
Content = List; |
||||
|
ViewModel = new ExampleViewModel(); |
||||
|
this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public AutoDataTemplateBindingHookTest() |
||||
|
{ |
||||
|
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); |
||||
|
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); |
||||
|
Locator.CurrentMutable.Register(() => new NestedView(), typeof(IViewFor<NestedViewModel>)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Apply_Data_Template_Binding_When_No_Template_Is_Set() |
||||
|
{ |
||||
|
var view = new ExampleView(); |
||||
|
Assert.NotNull(view.List.ItemTemplate); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Use_View_Model_View_Host_As_Data_Template() |
||||
|
{ |
||||
|
var view = new ExampleView(); |
||||
|
view.ViewModel.Items.Add(new NestedViewModel()); |
||||
|
|
||||
|
view.List.Template = GetTemplate(); |
||||
|
view.List.ApplyTemplate(); |
||||
|
view.List.Presenter.ApplyTemplate(); |
||||
|
|
||||
|
var child = view.List.Presenter.Panel.Children[0]; |
||||
|
var container = (ContentPresenter) child; |
||||
|
container.UpdateChild(); |
||||
|
|
||||
|
Assert.IsType<ViewModelViewHost>(container.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Resolve_And_Embedd_Appropriate_View_Model() |
||||
|
{ |
||||
|
var view = new ExampleView(); |
||||
|
var root = new TestRoot { Child = view }; |
||||
|
view.ViewModel.Items.Add(new NestedViewModel()); |
||||
|
|
||||
|
view.List.Template = GetTemplate(); |
||||
|
view.List.ApplyTemplate(); |
||||
|
view.List.Presenter.ApplyTemplate(); |
||||
|
|
||||
|
var child = view.List.Presenter.Panel.Children[0]; |
||||
|
var container = (ContentPresenter) child; |
||||
|
container.UpdateChild(); |
||||
|
|
||||
|
var host = (ViewModelViewHost) container.Child; |
||||
|
Assert.IsType<NestedViewModel>(host.ViewModel); |
||||
|
Assert.IsType<NestedViewModel>(host.DataContext); |
||||
|
|
||||
|
host.DataContext = "changed context"; |
||||
|
Assert.IsType<string>(host.ViewModel); |
||||
|
Assert.IsType<string>(host.DataContext); |
||||
|
} |
||||
|
|
||||
|
private FuncControlTemplate GetTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate<ItemsControl>(parent => |
||||
|
{ |
||||
|
return new Border |
||||
|
{ |
||||
|
Background = new Media.SolidColorBrush(0xffffffff), |
||||
|
Child = new ItemsPresenter |
||||
|
{ |
||||
|
Name = "PART_ItemsPresenter", |
||||
|
MemberSelector = parent.MemberSelector, |
||||
|
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], |
||||
|
} |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// 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 Avalonia.Controls; |
||||
|
using Avalonia.UnitTests; |
||||
|
using ReactiveUI; |
||||
|
using Splat; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI.UnitTests |
||||
|
{ |
||||
|
public class ReactiveUserControlTest |
||||
|
{ |
||||
|
public class ExampleViewModel : ReactiveObject { } |
||||
|
|
||||
|
public class ExampleView : ReactiveUserControl<ExampleViewModel> { } |
||||
|
|
||||
|
[Fact] |
||||
|
public void Data_Context_Should_Stay_In_Sync_With_Reactive_User_Control_View_Model() |
||||
|
{ |
||||
|
var view = new ExampleView(); |
||||
|
var viewModel = new ExampleViewModel(); |
||||
|
Assert.Null(view.ViewModel); |
||||
|
|
||||
|
view.DataContext = viewModel; |
||||
|
Assert.Equal(view.ViewModel, viewModel); |
||||
|
Assert.Equal(view.DataContext, viewModel); |
||||
|
|
||||
|
view.DataContext = null; |
||||
|
Assert.Null(view.ViewModel); |
||||
|
Assert.Null(view.DataContext); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// 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 Avalonia.Controls; |
||||
|
using Avalonia.UnitTests; |
||||
|
using ReactiveUI; |
||||
|
using Splat; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI.UnitTests |
||||
|
{ |
||||
|
public class ReactiveWindowTest |
||||
|
{ |
||||
|
public class ExampleViewModel : ReactiveObject { } |
||||
|
|
||||
|
public class ExampleWindow : ReactiveWindow<ExampleViewModel> { } |
||||
|
|
||||
|
[Fact] |
||||
|
public void Data_Context_Should_Stay_In_Sync_With_Reactive_Window_View_Model() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var view = new ExampleWindow(); |
||||
|
var viewModel = new ExampleViewModel(); |
||||
|
Assert.Null(view.ViewModel); |
||||
|
|
||||
|
view.DataContext = viewModel; |
||||
|
Assert.Equal(view.ViewModel, viewModel); |
||||
|
Assert.Equal(view.DataContext, viewModel); |
||||
|
|
||||
|
view.DataContext = null; |
||||
|
Assert.Null(view.ViewModel); |
||||
|
Assert.Null(view.DataContext); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// 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.Linq; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Presenters; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Avalonia.VisualTree; |
||||
|
using ReactiveUI; |
||||
|
using Splat; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.ReactiveUI.UnitTests |
||||
|
{ |
||||
|
public class TransitioningContentControlTest |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Transitioning_Control_Should_Derive_Template_From_Content_Control() |
||||
|
{ |
||||
|
var target = new TransitioningContentControl(); |
||||
|
var stylable = (IStyledElement)target; |
||||
|
Assert.Equal(typeof(ContentControl),stylable.StyleKey); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Transitioning_Control_Template_Should_Be_Instantiated() |
||||
|
{ |
||||
|
var target = new TransitioningContentControl |
||||
|
{ |
||||
|
PageTransition = null, |
||||
|
Template = GetTemplate(), |
||||
|
Content = "Foo" |
||||
|
}; |
||||
|
target.ApplyTemplate(); |
||||
|
((ContentPresenter)target.Presenter).UpdateChild(); |
||||
|
|
||||
|
var child = ((IVisual)target).VisualChildren.Single(); |
||||
|
Assert.IsType<Border>(child); |
||||
|
child = child.VisualChildren.Single(); |
||||
|
Assert.IsType<ContentPresenter>(child); |
||||
|
child = child.VisualChildren.Single(); |
||||
|
Assert.IsType<TextBlock>(child); |
||||
|
} |
||||
|
|
||||
|
private FuncControlTemplate GetTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate<ContentControl>(parent => |
||||
|
{ |
||||
|
return new Border |
||||
|
{ |
||||
|
Background = new Media.SolidColorBrush(0xffffffff), |
||||
|
Child = new ContentPresenter |
||||
|
{ |
||||
|
Name = "PART_ContentPresenter", |
||||
|
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], |
||||
|
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], |
||||
|
} |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
// 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 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<FirstViewModel> { } |
||||
|
|
||||
|
public class SecondViewModel : ReactiveObject { } |
||||
|
|
||||
|
public class SecondView : ReactiveUserControl<SecondViewModel> { } |
||||
|
|
||||
|
public ViewModelViewHostTest() |
||||
|
{ |
||||
|
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); |
||||
|
Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor<FirstViewModel>)); |
||||
|
Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor<SecondViewModel>)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel() |
||||
|
{ |
||||
|
var defaultContent = new TextBlock(); |
||||
|
var host = new ViewModelViewHost |
||||
|
{ |
||||
|
DefaultContent = defaultContent, |
||||
|
PageTransition = 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue