From 43fe090fa945203e1c4680e53e6c75e733f8f5c7 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Wed, 7 May 2014 13:20:36 +0100 Subject: [PATCH] Add more unit tests --- Rakefile | 2 +- Stylet/BootstrapperBase.cs | 1 + Stylet/View.cs | 11 +- Stylet/ViewManager.cs | 4 +- Stylet/WeakEventManager.cs | 8 +- .../BoolToVisibilityConverterTests.cs | 97 ++++++++ StyletUnitTests/ScreenExtensionTests.cs | 109 +++++++++ StyletUnitTests/StyletUnitTests.csproj | 4 + StyletUnitTests/ViewManagerTests.cs | 219 ++++++++++++++++++ StyletUnitTests/ViewTests.cs | 70 ++++++ 10 files changed, 510 insertions(+), 15 deletions(-) create mode 100644 StyletUnitTests/BoolToVisibilityConverterTests.cs create mode 100644 StyletUnitTests/ScreenExtensionTests.cs create mode 100644 StyletUnitTests/ViewManagerTests.cs create mode 100644 StyletUnitTests/ViewTests.cs diff --git a/Rakefile b/Rakefile index 685f612..91d22e9 100644 --- a/Rakefile +++ b/Rakefile @@ -46,7 +46,7 @@ end desc "Generate code coverage reports for CONFIG (or Debug)" task :cover => [:build, COVERAGE_DIR] do |t| sh OPENCOVER_CONSOLE, %Q{-register:user -target:"#{NUNIT_CONSOLE}" -filter:+[Stylet]* -targetargs:"#{UNIT_TESTS_DLL} /noshadow" -output:"#{COVERAGE_FILE}"} - sh REPORT_GENERATOR, %Q{"-reports:#{COVERAGE_FILE}" "-targetdir:#{COVERAGE_DIR}"} + sh REPORT_GENERATOR, %Q{-reports:"#{COVERAGE_FILE}" "-targetdir:#{COVERAGE_DIR}"} rm 'TestResult.xml' end diff --git a/Stylet/BootstrapperBase.cs b/Stylet/BootstrapperBase.cs index ffa885f..9dcd594 100644 --- a/Stylet/BootstrapperBase.cs +++ b/Stylet/BootstrapperBase.cs @@ -55,6 +55,7 @@ namespace Stylet this.ConfigureResources(); this.Configure(); + View.ViewManager = IoC.Get(); IoC.Get().ShowWindow(IoC.Get()); } diff --git a/Stylet/View.cs b/Stylet/View.cs index 1a3f163..7624a10 100644 --- a/Stylet/View.cs +++ b/Stylet/View.cs @@ -12,14 +12,7 @@ namespace Stylet public class View : DependencyObject { private static readonly ContentPropertyAttribute defaultContentProperty = new ContentPropertyAttribute("Content"); - private static IViewManager viewManager; - - static View() - { - // Don't complain that IoC is not initialized if we're in design mode - if (!Execute.InDesignMode) - viewManager = IoC.Get(); - } + public static IViewManager ViewManager; public static object GetActionTarget(DependencyObject obj) { @@ -45,7 +38,7 @@ namespace Stylet } public static readonly DependencyProperty ModelProperty = - DependencyProperty.RegisterAttached("Model", typeof(object), typeof(View), new PropertyMetadata(null, (d, e) => viewManager.OnModelChanged(d, e) )); + DependencyProperty.RegisterAttached("Model", typeof(object), typeof(View), new PropertyMetadata(null, (d, e) => ViewManager.OnModelChanged(d, e) )); public static void SetContentProperty(DependencyObject targetLocation, UIElement view) diff --git a/Stylet/ViewManager.cs b/Stylet/ViewManager.cs index ad31e34..2287a23 100644 --- a/Stylet/ViewManager.cs +++ b/Stylet/ViewManager.cs @@ -63,7 +63,7 @@ namespace Stylet } } - public virtual Type LocalViewForModel(Type modelType) + public virtual Type LocateViewForModel(Type modelType) { var viewName = Regex.Replace(modelType.FullName, @"ViewModel", "View"); // TODO: This might need some more thinking @@ -77,7 +77,7 @@ namespace Stylet public virtual UIElement CreateViewForModel(object model) { - var viewType = this.LocalViewForModel(model.GetType()); + var viewType = this.LocateViewForModel(model.GetType()); if (viewType.IsInterface || viewType.IsAbstract || !typeof(UIElement).IsAssignableFrom(viewType)) throw new Exception(String.Format("Found type for view: {0}, but it wasn't a class derived from UIElement", viewType.Name)); diff --git a/Stylet/WeakEventManager.cs b/Stylet/WeakEventManager.cs index dd6c390..dcf80fc 100644 --- a/Stylet/WeakEventManager.cs +++ b/Stylet/WeakEventManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Text; @@ -37,10 +38,11 @@ namespace Stylet internal void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { TSource source; - if (this.source.TryGetTarget(out source)) + var got = this.source.TryGetTarget(out source); + // We should never hit this case. The PropertyChangedeventManager shouldn't call us if the source became null + Debug.Assert(got); + if (got) this.handler(this.valueSelector(source)); - else - this.remover(this); } public void Unbind() diff --git a/StyletUnitTests/BoolToVisibilityConverterTests.cs b/StyletUnitTests/BoolToVisibilityConverterTests.cs new file mode 100644 index 0000000..e6aab4c --- /dev/null +++ b/StyletUnitTests/BoolToVisibilityConverterTests.cs @@ -0,0 +1,97 @@ +using NUnit.Framework; +using Stylet.Xaml; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace StyletUnitTests +{ + [TestFixture] + public class BoolToVisibilityConverterTests + { + private BoolToVisibilityConverter converter; + + [SetUp] + public void SetUp() + { + this.converter = new BoolToVisibilityConverter(); + } + + [Test] + public void InstanceReturnsSingleton() + { + Assert.IsNotNull(BoolToVisibilityConverter.Instance); + Assert.AreEqual(BoolToVisibilityConverter.Instance, BoolToVisibilityConverter.Instance); + } + + [Test] + public void ConvertReturnsNullIfValueNotBool() + { + var result = this.converter.Convert(new object(), null, null, null); + Assert.Null(result); + } + + [Test] + public void ConvertReturnsVisibleForTrueByDefault() + { + var result = this.converter.Convert(true, null, null, null); + Assert.AreEqual(Visibility.Visible, result); + } + + [Test] + public void ConvertReturnsSetTrueVisibility() + { + this.converter.TrueVisibility = Visibility.Hidden; + var result = this.converter.Convert(true, null, null, null); + Assert.AreEqual(Visibility.Hidden, result); + } + + [Test] + public void ConvertReturnsCollapsedForFalseByDefault() + { + var result = this.converter.Convert(false, null, null, null); + Assert.AreEqual(Visibility.Collapsed, result); + } + + [Test] + public void ConvertReturnsSetFalseVisibility() + { + this.converter.FalseVisibility = Visibility.Visible; + var result = this.converter.Convert(false, null, null, null); + Assert.AreEqual(Visibility.Visible, result); + } + + [Test] + public void ConvertBackReturnsTrueIfValueIsTrueVisibility() + { + this.converter.TrueVisibility = Visibility.Hidden; + var result = this.converter.ConvertBack(Visibility.Hidden, null, null, null); + Assert.AreEqual(true, result); + } + + [Test] + public void ConvertBackReturnsFalseIfValueIsFalseVisibility() + { + this.converter.FalseVisibility = Visibility.Hidden; + var result = this.converter.ConvertBack(Visibility.Hidden, null, null, null); + Assert.AreEqual(false, result); + } + + [Test] + public void ConvertBackReturnsNullIfValueIsNotAVisibility() + { + var result = this.converter.ConvertBack(new object(), null, null, null); + Assert.Null(result); + } + + [Test] + public void ConvertBackReturnsNullIfValueIsNotAConfiguredVisibility() + { + var result = this.converter.ConvertBack(Visibility.Hidden, null, null, null); + Assert.Null(result); + } + } +} diff --git a/StyletUnitTests/ScreenExtensionTests.cs b/StyletUnitTests/ScreenExtensionTests.cs new file mode 100644 index 0000000..4c75623 --- /dev/null +++ b/StyletUnitTests/ScreenExtensionTests.cs @@ -0,0 +1,109 @@ +using Moq; +using NUnit.Framework; +using Stylet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StyletUnitTests +{ + [TestFixture] + public class ScreenExtensionTests + { + private Screen parent; + private Mock child; + + [SetUp] + public void SetUp() + { + this.parent = new Screen(); + this.child = new Mock(); + } + + [Test] + public void TryActivateActivatesIActivate() + { + var screen = new Mock(); + ScreenExtensions.TryActivate(screen.Object); + screen.Verify(x => x.Activate()); + } + + [Test] + public void TryActivateDoesNothingToNonIActivate() + { + var screen = new Mock(MockBehavior.Strict); + ScreenExtensions.TryActivate(screen.Object); + } + + [Test] + public void TryDeactivateDeactivatesIDeactivate() + { + var screen = new Mock(); + ScreenExtensions.TryDeactivate(screen.Object); + screen.Verify(x => x.Deactivate()); + } + + [Test] + public void TryDeactivateDoesNothingToNonIDeactivate() + { + var screen = new Mock(MockBehavior.Strict); + ScreenExtensions.TryDeactivate(screen.Object); + } + + [Test] + public void TryCloseClosesIClose() + { + var screen = new Mock(); + ScreenExtensions.TryClose(screen.Object); + screen.Verify(x => x.Close()); + } + + [Test] + public void TryCloseDoesNothingToNonIClose() + { + var screen = new Mock(MockBehavior.Strict); + ScreenExtensions.TryClose(screen.Object); + } + + [Test] + public void ConductWithActivates() + { + this.child.Object.ConductWith(this.parent); + ((IActivate)this.parent).Activate(); + this.child.Verify(x => x.Activate()); + } + + [Test] + public void ConductWithDeactivates() + { + // Needs to be active.... + ((IActivate)this.parent).Activate(); + this.child.Object.ConductWith(this.parent); + ((IDeactivate)this.parent).Deactivate(); + this.child.Verify(x => x.Deactivate()); + } + + [Test] + public void ConductWithCloses() + { + this.child.Object.ConductWith(this.parent); + ((IClose)this.parent).Close(); + this.child.Verify(x => x.Close()); + } + + [Test] + public void ConductWithDoesNotRetain() + { + var child = new Screen(); + child.ConductWith(this.parent); + + var weakChild = new WeakReference(child); + child = null; + GC.Collect(); + + Assert.Null(weakChild.Target); + } + } +} diff --git a/StyletUnitTests/StyletUnitTests.csproj b/StyletUnitTests/StyletUnitTests.csproj index 83daf32..0c9b652 100644 --- a/StyletUnitTests/StyletUnitTests.csproj +++ b/StyletUnitTests/StyletUnitTests.csproj @@ -54,6 +54,7 @@ + @@ -67,6 +68,7 @@ + @@ -77,6 +79,8 @@ + + diff --git a/StyletUnitTests/ViewManagerTests.cs b/StyletUnitTests/ViewManagerTests.cs new file mode 100644 index 0000000..563a837 --- /dev/null +++ b/StyletUnitTests/ViewManagerTests.cs @@ -0,0 +1,219 @@ +using Moq; +using NUnit.Framework; +using Stylet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace StyletUnitTests +{ + public class ViewManagerTestsViewModel + { + } + + public class ViewManagerTestsView + { + } + + [TestFixture, RequiresSTA] + public class ViewManagerTests + { + private interface I1 { } + private abstract class AC1 { } + private class C1 { } + + private class CreatingAndBindingViewManager : ViewManager + { + public UIElement View; + public object RequestedModel; + public override UIElement CreateViewForModel(object model) + { + this.RequestedModel = model; + return this.View; + } + + public UIElement BindViewToModelView; + public object BindViewtoModelViewModel; + public override void BindViewToModel(UIElement view, object viewModel) + { + this.BindViewToModelView = view; + this.BindViewtoModelViewModel = viewModel; + } + } + + private class LocatingViewManager : ViewManager + { + public Type LocatedViewType; + public override Type LocateViewForModel(Type modelType) + { + return this.LocatedViewType; + } + } + + + private class TestView : UIElement + { + public bool InitializeComponentCalled; + public void InitializeComponent() + { + this.InitializeComponentCalled = true; + } + } + + private ViewManager viewManager; + + [SetUp] + public void SetUp() + { + this.viewManager = new ViewManager(); + } + + [Test] + public void OnModelChangedDoesNothingIfNoChange() + { + var val = new object(); + this.viewManager.OnModelChanged(null, new DependencyPropertyChangedEventArgs(View.ModelProperty, val, val)); + } + + [Test] + public void OnModelChangedSetsNullIfNewValueNull() + { + var target = new ContentControl(); + this.viewManager.OnModelChanged(target, new DependencyPropertyChangedEventArgs(View.ModelProperty, 5, null)); + Assert.Null(target.Content); + } + + [Test] + public void OnModelChangedUsesViewIfAlreadySet() + { + var target = new ContentControl(); + var model = new Mock(); + var view = new UIElement(); + + model.Setup(x => x.View).Returns(view); + this.viewManager.OnModelChanged(target, new DependencyPropertyChangedEventArgs(View.ModelProperty, null, model.Object)); + + Assert.AreEqual(view, target.Content); + } + + [Test] + public void OnModelChangedCreatesAndBindsView() + { + var target = new ContentControl(); + var model = new object(); + var view = new UIElement(); + var viewManager = new CreatingAndBindingViewManager(); + + viewManager.View = view; + + viewManager.OnModelChanged(target, new DependencyPropertyChangedEventArgs(View.ModelProperty, null, model)); + + Assert.AreEqual(viewManager.RequestedModel, model); + Assert.AreEqual(viewManager.BindViewToModelView, view); + Assert.AreEqual(viewManager.BindViewtoModelViewModel, model); + Assert.AreEqual(view, target.Content); + } + + [Test] + public void LocateViewforModelThrowsIfViewNotFound() + { + Assert.Throws(() => this.viewManager.LocateViewForModel(typeof(C1))); + } + + [Test] + public void LocateViewforModelFindsViewForModel() + { + Execute.TestExecuteSynchronously = true; + AssemblySource.Assemblies.Add(Assembly.GetExecutingAssembly()); + var viewType = this.viewManager.LocateViewForModel(typeof(ViewManagerTestsViewModel)); + Assert.AreEqual(typeof(ViewManagerTestsView), viewType); + } + + [Test] + public void CreateViewForModelThrowsIfViewIsNotConcreteUIElement() + { + var viewManager = new LocatingViewManager(); + + viewManager.LocatedViewType = typeof(I1); + Assert.Throws(() => viewManager.CreateViewForModel(new object())); + + viewManager.LocatedViewType = typeof(AC1); + Assert.Throws(() => viewManager.CreateViewForModel(new object())); + + viewManager.LocatedViewType = typeof(C1); + Assert.Throws(() => viewManager.CreateViewForModel(new object())); + } + + [Test] + public void CreateViewForModelCallsFetchesViewAndCallsInitializeComponent() + { + var view = new TestView(); + IoC.GetInstance = (t, k) => + { + Assert.AreEqual(typeof(TestView), t); + Assert.Null(k); + return view; + }; + var viewManager = new LocatingViewManager(); + viewManager.LocatedViewType = typeof(TestView); + + var returnedView = viewManager.CreateViewForModel(new object()); + + Assert.True(view.InitializeComponentCalled); + Assert.AreEqual(view, returnedView); + } + + [Test] + public void CreateViewForModelDoesNotComplainIfNoInitializeComponentMethod() + { + var view = new UIElement(); + IoC.GetInstance = (t, k) => + { + Assert.AreEqual(typeof(UIElement), t); + Assert.Null(k); + return view; + }; + var viewManager = new LocatingViewManager(); + viewManager.LocatedViewType = typeof(UIElement); + + var returnedView = viewManager.CreateViewForModel(new object()); + + Assert.AreEqual(view, returnedView); + } + + [Test] + public void BindViewToModelSetsActionTarget() + { + var view = new UIElement(); + var model = new object(); + this.viewManager.BindViewToModel(view, model); + + Assert.AreEqual(model, View.GetActionTarget(view)); + } + + [Test] + public void BindViewToModelSetsDataContext() + { + var view = new FrameworkElement(); + var model = new object(); + this.viewManager.BindViewToModel(view, model); + + Assert.AreEqual(model, view.DataContext); + } + + [Test] + public void BindViewToModelAttachesView() + { + var view = new UIElement(); + var model = new Mock(); + this.viewManager.BindViewToModel(view, model.Object); + + model.Verify(x => x.AttachView(view)); + } + } +} diff --git a/StyletUnitTests/ViewTests.cs b/StyletUnitTests/ViewTests.cs new file mode 100644 index 0000000..bc5f95a --- /dev/null +++ b/StyletUnitTests/ViewTests.cs @@ -0,0 +1,70 @@ +using Moq; +using NUnit.Framework; +using Stylet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace StyletUnitTests +{ + [TestFixture, RequiresSTA] + public class ViewTests + { + private Mock viewManager; + + [SetUp] + public void SetUp() + { + this.viewManager = new Mock(); + View.ViewManager = this.viewManager.Object; + } + + [Test] + public void ActionTargetStores() + { + var obj = new DependencyObject(); + View.SetActionTarget(obj, 5); + Assert.AreEqual(5, View.GetActionTarget(obj)); + } + + [Test] + public void ModelStores() + { + var obj = new DependencyObject(); + View.SetModel(obj, 5); + Assert.AreEqual(5, View.GetModel(obj)); + } + + [Test] + public void ChangingModelCallsOnModelChanged() + { + var obj = new DependencyObject(); + var model = new object(); + View.SetModel(obj, null); + + DependencyPropertyChangedEventArgs ea = default(DependencyPropertyChangedEventArgs); + this.viewManager.Setup(x => x.OnModelChanged(obj, It.IsAny())) + .Callback((d, e) => ea = e).Verifiable(); + View.SetModel(obj, model); + + this.viewManager.Verify(); + Assert.Null(ea.OldValue); + Assert.AreEqual(model, ea.NewValue); + Assert.AreEqual(View.ModelProperty, ea.Property); + } + + [Test] + public void SetsContentControlContentProperty() + { + var obj = new ContentControl(); + var view = new UIElement(); + + View.SetContentProperty(obj, view); + Assert.AreEqual(obj.Content, view); + } + } +}