diff --git a/Rakefile b/Rakefile index 32023f2..d47b023 100644 --- a/Rakefile +++ b/Rakefile @@ -47,7 +47,7 @@ task :test => [:nunit_test_runner] do |t| end desc "Launch the NUnit gui pointing at the correct DLL for CONFIG (or Debug)" -task :nunit do |t| +task :nunit => [:test_environment] do |t| sh NUNIT_EXE, UNIT_TESTS_DLL end diff --git a/Stylet/BootstrapperBase.cs b/Stylet/BootstrapperBase.cs index e8ad8fd..8526ff0 100644 --- a/Stylet/BootstrapperBase.cs +++ b/Stylet/BootstrapperBase.cs @@ -30,13 +30,20 @@ namespace Stylet this.Application = Application.Current; - // Call this before calling our Start method - this.Application.Startup += this.OnStartup; - this.Application.Startup += (o, e) => this.Start(); + // Allows for unit testing + if (this.Application != null) + { + // Call this before calling our Start method + this.Application.Startup += (o, e) => + { + this.OnStartup(o, e); + this.Start(); + }; - // Make life nice for the app - they can handle these by overriding Bootstrapper methods, rather than adding event handlers - this.Application.Exit += OnExit; - this.Application.DispatcherUnhandledException += OnUnhandledExecption; + // Make life nice for the app - they can handle these by overriding Bootstrapper methods, rather than adding event handlers + this.Application.Exit += OnExit; + this.Application.DispatcherUnhandledException += OnUnhandledExecption; + } } /// @@ -61,8 +68,11 @@ namespace Stylet protected virtual void ConfigureResources() { + if (this.Application == null) + return; + var rc = new ResourceDictionary() { Source = new Uri("pack://application:,,,/Stylet;component/Xaml/StyletResourceDictionary.xaml", UriKind.Absolute) }; - Application.Resources.MergedDictionaries.Add(rc); + this.Application.Resources.MergedDictionaries.Add(rc); } /// @@ -95,9 +105,9 @@ namespace Stylet /// Initial contents of AssemblySource.Assemblies, defaults to the entry assembly /// /// - protected IEnumerable SelectAssemblies() + protected virtual IEnumerable SelectAssemblies() { - return new[] { Assembly.GetEntryAssembly() }; + return new[] { this.GetType().Assembly }; } /// @@ -110,7 +120,7 @@ namespace Stylet /// /// /// - protected virtual void OnExit(object sender, EventArgs e) { } + protected virtual void OnExit(object sender, ExitEventArgs e) { } /// /// Hook called on an unhandled exception diff --git a/Stylet/StyletIoC/BuilderUpper.cs b/Stylet/StyletIoC/BuilderUpper.cs index 9d5b1a6..43a052c 100644 --- a/Stylet/StyletIoC/BuilderUpper.cs +++ b/Stylet/StyletIoC/BuilderUpper.cs @@ -43,7 +43,9 @@ namespace StyletIoC var memberAccess = Expression.MakeMemberAccess(objExpression, member); var memberValue = this.container.GetExpression(new TypeKey(memberType, attribute.Key), true); - return Expression.Assign(memberAccess, memberValue); + var assign = Expression.Assign(memberAccess, memberValue); + // Only actually do the assignment if the field/property is currently null + return Expression.IfThen(Expression.Equal(memberAccess, Expression.Constant(null, memberType)), assign); } public Action GetImplementor() diff --git a/Stylet/StyletIoC/StyletIoCBuilder.cs b/Stylet/StyletIoC/StyletIoCBuilder.cs index d2b1d45..4a4ac14 100644 --- a/Stylet/StyletIoC/StyletIoCBuilder.cs +++ b/Stylet/StyletIoC/StyletIoCBuilder.cs @@ -255,7 +255,7 @@ namespace StyletIoC public override void Build(StyletIoCContainer container) { - var candidates = from type in assemblies.SelectMany(x => x.GetTypes()) + var candidates = from type in assemblies.Distinct().SelectMany(x => x.GetTypes()) let baseType = type.GetBaseTypesAndInterfaces().FirstOrDefault(x => x == this.serviceType || x.IsGenericType && x.GetGenericTypeDefinition() == this.serviceType) where baseType != null select new { Type = type, Base = baseType.ContainsGenericParameters ? baseType.GetGenericTypeDefinition() : baseType }; @@ -377,18 +377,13 @@ namespace StyletIoC assemblies = new[] { Assembly.GetCallingAssembly() }; // We self-bind concrete classes only - var classes = assemblies.SelectMany(x => x.GetTypes()).Where(c => c.IsClass && !c.IsAbstract); + var classes = assemblies.Distinct().SelectMany(x => x.GetTypes()).Where(c => c.IsClass && !c.IsAbstract); foreach (var cls in classes) { - // Don't care if binding fails - we're likely to hit a few of these - try - { - this.BindWeak(cls).To(cls); - } - catch (StyletIoCRegistrationException e) - { - Debug.WriteLine(String.Format("Unable to auto-bind type {0}: {1}", cls.Description(), e.Message), "StyletIoC"); - } + // It's not actually possible for this to fail with a StyletIoCRegistrationException (at least currently) + // It's a self-binding, and those are always safe (at this stage - it could fall over when the containing's actually build) + + this.BindWeak(cls).To(cls); } } @@ -412,7 +407,7 @@ namespace StyletIoC { var container = new StyletIoCContainer(); container.AddRegistration(new TypeKey(typeof(IContainer), null), new SingletonRegistration(new FactoryCreator(c => container, container))); - container.AddRegistration(new TypeKey(typeof(StyletIoCContainer), null), new SingletonRegistration(new FactoryCreator(c => container, container))); + //container.AddRegistration(new TypeKey(typeof(StyletIoCContainer), null), new SingletonRegistration(new FactoryCreator(c => container, container))); // For each TypeKey, we remove any weak bindings if there are any strong bindings var groups = this.bindings.GroupBy(x => new { Key = x.Key, Type = x.ServiceType }); diff --git a/Stylet/StyletIoC/StyletIoCContainer.cs b/Stylet/StyletIoC/StyletIoCContainer.cs index d028d4d..7f6190a 100644 --- a/Stylet/StyletIoC/StyletIoCContainer.cs +++ b/Stylet/StyletIoC/StyletIoCContainer.cs @@ -174,8 +174,9 @@ namespace StyletIoC throw new ArgumentNullException("type"); var typeKey = new TypeKey(type, key); IRegistration registration; - if (!this.TryRetrieveGetAllRegistrationFromElementType(typeKey, null, out registration)) - throw new StyletIoCRegistrationException(String.Format("Could not find registration for type {0} and key '{1}'", typeKey.Type.Description(), typeKey.Key)); + // This can currently never fail, since we pass in null + var result = this.TryRetrieveGetAllRegistrationFromElementType(typeKey, null, out registration); + Debug.Assert(result); var generator = registration.GetGenerator(); return (IEnumerable)generator(); } @@ -345,8 +346,8 @@ namespace StyletIoC newType = unboundGeneric.Type.MakeGenericType(unboundGeneric.Type.GetTypeInfo().GenericTypeParameters.Select(x => mapping.Single(t => t.Name.Name == x.Name).Type).ToArray()); } - if (!type.IsAssignableFrom(newType)) - continue; + // The binder should have made sure of this + Debug.Assert(type.IsAssignableFrom(newType)); // Right! We've made a new generic type we can use var registration = unboundGeneric.CreateRegistrationForType(newType); @@ -571,14 +572,9 @@ namespace StyletIoC public static IEnumerable GetBaseTypes(this Type type) { - if (type == typeof(object)) - yield break; - var baseType = type.BaseType ?? typeof(object); - - while (baseType != null) + for (var baseType = type.BaseType; baseType != null; baseType = baseType.BaseType) { yield return baseType; - baseType = baseType.BaseType; } } @@ -635,7 +631,6 @@ namespace StyletIoC public class StyletIoCFindConstructorException : StyletIoCException { public StyletIoCFindConstructorException(string message) : base(message) { } - public StyletIoCFindConstructorException(string message, Exception innerException) : base(message, innerException) { } } public class StyletIoCCreateFactoryException : StyletIoCException diff --git a/Stylet/WindowManager.cs b/Stylet/WindowManager.cs index f7647cc..0d367f3 100644 --- a/Stylet/WindowManager.cs +++ b/Stylet/WindowManager.cs @@ -18,6 +18,13 @@ namespace Stylet public class WindowManager : IWindowManager { + private IViewManager viewManager; + + public WindowManager(IViewManager viewManager) + { + this.viewManager = viewManager; + } + public void ShowWindow(object viewModel) { this.CreateWindow(viewModel, false).Show(); @@ -28,16 +35,14 @@ namespace Stylet return this.CreateWindow(viewModel, true).ShowDialog(); } - private Window CreateWindow(object viewModel, bool isDialog) + protected virtual Window CreateWindow(object viewModel, bool isDialog) { - var viewManager = IoC.Get(); - - var view = viewManager.CreateViewForModel(viewModel); + var view = this.viewManager.CreateViewForModel(viewModel); var window = view as Window; if (window == null) throw new Exception(String.Format("Tried to show {0} as a window, but it isn't a Window", view == null ? "(null)" : view.GetType().Name)); - viewManager.BindViewToModel(window, viewModel); + this.viewManager.BindViewToModel(window, viewModel); var haveDisplayName = viewModel as IHaveDisplayName; if (haveDisplayName != null) @@ -139,9 +144,8 @@ namespace Stylet if (await task) { this.window.Closing -= this.WindowClosing; - this.window.StateChanged -= this.WindowStateChanged; - ScreenExtensions.TryClose(this.viewModel); this.window.Close(); + // The Closed event handler handles unregistering the events, and closing the ViewModel } } } diff --git a/StyletIntegrationTests/Actions/ActionsView.xaml b/StyletIntegrationTests/Actions/ActionsView.xaml deleted file mode 100644 index e52ef56..0000000 --- a/StyletIntegrationTests/Actions/ActionsView.xaml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Verify that the checkbox enables/disables the button, and that clicking the button shows a message box, containing the parameter in the textbox. - - - - Enabled - Parameter - - - - - - - Verify that both buttons show message boxes. - - - - - - - - - diff --git a/StyletIntegrationTests/Actions/ActionsViewModel.cs b/StyletIntegrationTests/Actions/ActionsViewModel.cs deleted file mode 100644 index e8dd316..0000000 --- a/StyletIntegrationTests/Actions/ActionsViewModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -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 StyletIntegrationTests.Actions -{ - public class ActionsViewModel : Screen - { - private bool _checkboxIsChecked; - public bool CheckboxIsChecked - { - get { return this._checkboxIsChecked; } - set - { - this.SetAndNotify(ref this._checkboxIsChecked, value); - this.NotifyOfPropertyChange(() => this.CanCommandButton); - } - } - - public ActionsViewModel() - { - this.DisplayName = "Actions"; - } - - public bool CanCommandButton - { - get { return this.CheckboxIsChecked; } - } - public void CommandButton(string parameter) - { - MessageBox.Show(String.Format("Parameter is '{0}'", parameter)); - } - - public void EventButtonNoArgs() - { - MessageBox.Show("Success!"); - } - - public void EventButtonWithArgs(RoutedEventArgs e) - { - string buttonText = (string)(((Button)e.Source).Content); - MessageBox.Show(buttonText == "Button 2" ? "Success!" : "Fail: Sender was not sent successfully"); - } - } -} diff --git a/StyletIntegrationTests/Bootstrapper.cs b/StyletIntegrationTests/Bootstrapper.cs index 2329678..3bc3491 100644 --- a/StyletIntegrationTests/Bootstrapper.cs +++ b/StyletIntegrationTests/Bootstrapper.cs @@ -1,5 +1,4 @@ using Stylet; -using StyletIntegrationTests.BootstrapperIoC; using System; using System.Collections.Generic; using System.Linq; @@ -12,12 +11,6 @@ namespace StyletIntegrationTests { public class Bootstrapper : Bootstrapper { - protected override void ConfigureIoC(StyletIoC.IStyletIoCBuilder builder) - { - base.ConfigureIoC(builder); // Calling this just to trigger some code coverage - builder.Bind().ToAllImplementations(); - } - protected override void OnUnhandledExecption(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { base.OnUnhandledExecption(sender, e); // Calling this just to trigger some code coverage diff --git a/StyletIntegrationTests/BootstrapperIoC/WindowView.xaml b/StyletIntegrationTests/BootstrapperIoC/WindowView.xaml deleted file mode 100644 index 4725c45..0000000 --- a/StyletIntegrationTests/BootstrapperIoC/WindowView.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - Make sure that pressing the following buttons has no visible effect (no dialogs, crashes, etc). - - - - - diff --git a/StyletIntegrationTests/BootstrapperIoC/WindowView.xaml.cs b/StyletIntegrationTests/BootstrapperIoC/WindowView.xaml.cs deleted file mode 100644 index c663c1a..0000000 --- a/StyletIntegrationTests/BootstrapperIoC/WindowView.xaml.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; - -namespace StyletIntegrationTests.BootstrapperIoC -{ - /// - /// Interaction logic for WindowView.xaml - /// - public partial class WindowView : Window - { - public WindowView() - { - InitializeComponent(); - } - } -} diff --git a/StyletIntegrationTests/BootstrapperIoC/WindowViewModel.cs b/StyletIntegrationTests/BootstrapperIoC/WindowViewModel.cs deleted file mode 100644 index 6145a52..0000000 --- a/StyletIntegrationTests/BootstrapperIoC/WindowViewModel.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Stylet; -using StyletIoC; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StyletIntegrationTests.BootstrapperIoC -{ - interface BootstrapperIoCI1 { } - class BootstrapperIoCC1 : BootstrapperIoCI1 { } - class BootstrapperIoCC2 : BootstrapperIoCI1 { } - class BootstrapperIoCC3 - { - [Inject] - public BootstrapperIoCC1 C1 { get; set; } - } - - public class WindowViewModel : Screen - { - public void GetSingle() - { - var result = IoC.Get(); - if (result == null) - throw new Exception("IoC.Get failed"); - } - - public void GetAll() - { - var result = IoC.GetAll().ToList(); - if (result.Count != 2 || !(result[0] is BootstrapperIoCC1) || !(result[1] is BootstrapperIoCC2)) - throw new Exception("IoC.GetAll failed"); - } - - public void BuildUp() - { - var c3 = new BootstrapperIoCC3(); - IoC.BuildUp(c3); - if (c3.C1 == null) - throw new Exception("IoC.BuildUp failed"); - } - } -} diff --git a/StyletIntegrationTests/OnUnhandledException/WindowView.xaml b/StyletIntegrationTests/OnUnhandledException/WindowView.xaml deleted file mode 100644 index c6c0358..0000000 --- a/StyletIntegrationTests/OnUnhandledException/WindowView.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - Verify that pressing the button below shows a dialog, containing the message 'Unhandled Exception: Hello'. - - - diff --git a/StyletIntegrationTests/OnUnhandledException/WindowView.xaml.cs b/StyletIntegrationTests/OnUnhandledException/WindowView.xaml.cs deleted file mode 100644 index 67551f5..0000000 --- a/StyletIntegrationTests/OnUnhandledException/WindowView.xaml.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; - -namespace StyletIntegrationTests.OnUnhandledException -{ - /// - /// Interaction logic for WindowView.xaml - /// - public partial class WindowView : Window - { - public WindowView() - { - InitializeComponent(); - } - } -} diff --git a/StyletIntegrationTests/OnUnhandledException/WindowViewModel.cs b/StyletIntegrationTests/OnUnhandledException/WindowViewModel.cs deleted file mode 100644 index d074ced..0000000 --- a/StyletIntegrationTests/OnUnhandledException/WindowViewModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Stylet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StyletIntegrationTests.OnUnhandledException -{ - public class WindowViewModel : Screen - { - public void ThrowException() - { - throw new Exception("Hello"); - } - } -} diff --git a/StyletIntegrationTests/ShellView.xaml b/StyletIntegrationTests/ShellView.xaml index 544e1df..0d165a1 100644 --- a/StyletIntegrationTests/ShellView.xaml +++ b/StyletIntegrationTests/ShellView.xaml @@ -12,28 +12,22 @@ - - - - - - - - - - - - - + + Verify that pressing the button below shows a dialog, containing the message 'Unhandled Exception: Hello'. + + - - + + + Verify that pressing the button shows a MessageBox saying 'Success'. + + diff --git a/StyletIntegrationTests/ShellViewModel.cs b/StyletIntegrationTests/ShellViewModel.cs index f17ac40..840cb77 100644 --- a/StyletIntegrationTests/ShellViewModel.cs +++ b/StyletIntegrationTests/ShellViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace StyletIntegrationTests { @@ -32,40 +33,36 @@ namespace StyletIntegrationTests this.ShowDialogAndDialogResultDialogResult = this.windowManager.ShowDialog(dialog); } - public void ShowWindowsDisplayNameBound() - { - var window = new WindowDisplayNameBound.WindowViewModel(); - this.windowManager.ShowWindow(window); - } - - public void ShowWindowGuardClose() - { - var window = new WindowGuardClose.WindowViewModel(); - this.windowManager.ShowWindow(window); - } - public void ShowWindowLifecycle() { var window = new WindowLifecycle.WindowViewModel(); this.windowManager.ShowWindow(window); } - public void ShowBootstrapperIoC() + public void ThrowException() { - var window = new BootstrapperIoC.WindowViewModel(); - this.windowManager.ShowWindow(window); + throw new Exception("Hello"); } - public void ShowOnUnhandledException() + public async void TestDispatcher() { - var window = new OnUnhandledException.WindowViewModel(); - this.windowManager.ShowWindow(window); - } + var dispatcher = Execute.Dispatcher; + var log = new List(); - public void ShowActions() - { - var window = new Actions.ActionsViewModel(); - this.windowManager.ShowDialog(window); + await Task.Run(() => dispatcher.Send(() => { lock(log) { log.Add("One"); }; })); + lock (log) { log.Add("Two"); }; + + await Task.Run(() => dispatcher.Post(() => { lock (log) { log.Add("Three"); }; })); + lock (log) { log.Add("Four"); }; + + // OK, so at this point there's a queued message saying to add Three to the log + // Give the main thread time to process that message + await Task.Delay(100); + + if (log.SequenceEqual(new[] { "One", "Two", "Four", "Three" })) + MessageBox.Show("Success"); + else + MessageBox.Show("Failure"); } } } diff --git a/StyletIntegrationTests/StyletIntegrationTests.csproj b/StyletIntegrationTests/StyletIntegrationTests.csproj index bc47de7..f0ab60f 100644 --- a/StyletIntegrationTests/StyletIntegrationTests.csproj +++ b/StyletIntegrationTests/StyletIntegrationTests.csproj @@ -55,16 +55,7 @@ - - - WindowView.xaml - - - - WindowView.xaml - - Code @@ -80,8 +71,6 @@ - - ResXFileCodeGenerator @@ -103,18 +92,6 @@ - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -123,14 +100,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - Designer MSBuild:Compile diff --git a/StyletIntegrationTests/WindowDisplayNameBound/WindowView.xaml b/StyletIntegrationTests/WindowDisplayNameBound/WindowView.xaml deleted file mode 100644 index f0836f8..0000000 --- a/StyletIntegrationTests/WindowDisplayNameBound/WindowView.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - Press the button, and verify that the count in the window title increases. When you are done, close the window. - - - diff --git a/StyletIntegrationTests/WindowDisplayNameBound/WindowViewModel.cs b/StyletIntegrationTests/WindowDisplayNameBound/WindowViewModel.cs deleted file mode 100644 index ae92826..0000000 --- a/StyletIntegrationTests/WindowDisplayNameBound/WindowViewModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Stylet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StyletIntegrationTests.WindowDisplayNameBound -{ - public class WindowViewModel : Screen - { - private int count = 0; - - public WindowViewModel() - { - this.DisplayName = String.Format("Count: {0}", this.count); - } - - public void AddCount() - { - this.count++; - this.DisplayName = String.Format("Count: {0}", this.count); - } - } -} diff --git a/StyletIntegrationTests/WindowGuardClose/WindowView.xaml b/StyletIntegrationTests/WindowGuardClose/WindowView.xaml deleted file mode 100644 index 6496f2e..0000000 --- a/StyletIntegrationTests/WindowGuardClose/WindowView.xaml +++ /dev/null @@ -1,11 +0,0 @@ - - - Leave the checkbox unchecked, then close the window using the red X at the top. It should not close. - Check the checkbox, then close the window again. It should close after 3 seconds. - Allow Close - - diff --git a/StyletIntegrationTests/WindowGuardClose/WindowViewModel.cs b/StyletIntegrationTests/WindowGuardClose/WindowViewModel.cs deleted file mode 100644 index ac3fb26..0000000 --- a/StyletIntegrationTests/WindowGuardClose/WindowViewModel.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Stylet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StyletIntegrationTests.WindowGuardClose -{ - public class WindowViewModel : Screen - { - public bool AllowClose { get; set; } - - public WindowViewModel() - { - this.DisplayName = "Window Guard Close"; - } - - public override Task CanCloseAsync() - { - return this.AllowClose ? Task.Delay(2000).ContinueWith(t => true) : Task.FromResult(false); - } - } -} diff --git a/StyletUnitTests/AssemblySourceTests.cs b/StyletUnitTests/AssemblySourceTests.cs index 57f0800..d9e88b8 100644 --- a/StyletUnitTests/AssemblySourceTests.cs +++ b/StyletUnitTests/AssemblySourceTests.cs @@ -16,6 +16,7 @@ namespace StyletUnitTests public void SetUpFixture() { Execute.TestExecuteSynchronously = true; + AssemblySource.Assemblies.Clear(); } [Test] diff --git a/StyletUnitTests/BootstrapperBaseTests.cs b/StyletUnitTests/BootstrapperBaseTests.cs new file mode 100644 index 0000000..843fd06 --- /dev/null +++ b/StyletUnitTests/BootstrapperBaseTests.cs @@ -0,0 +1,162 @@ +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; + +namespace StyletUnitTests +{ + [TestFixture, RequiresSTA] + public class BootstrapperBaseTests + { + private class RootViewModel { } + + private class MyBootstrapperBase : BootstrapperBase where T : class + { + private IViewManager viewManager; + private IWindowManager windowManager; + + public MyBootstrapperBase(IViewManager viewManager, IWindowManager windowManager) + { + this.viewManager = viewManager; + this.windowManager = windowManager; + } + + public bool GetInstanceCalled; + protected override object GetInstance(Type service, string key = null) + { + this.GetInstanceCalled = true; + if (service == typeof(IViewManager)) + return this.viewManager; + if (service == typeof(IWindowManager)) + return this.windowManager; + if (service == typeof(RootViewModel)) + return new RootViewModel(); + return new object(); + } + + public bool GetAllInstancesCalled; + protected override IEnumerable GetAllInstances(Type service) + { + this.GetAllInstancesCalled = true; + return Enumerable.Empty(); + } + + public bool BuildUpCalled; + protected override void BuildUp(object instance) + { + this.BuildUpCalled = true; + } + + public bool OnExitCalled; + protected override void OnExit(object sender, ExitEventArgs e) + { + this.OnExitCalled = true; + } + + public bool ConfigureResourcesCalled; + protected override void ConfigureResources() + { + this.ConfigureResourcesCalled = true; + base.ConfigureResources(); + } + + public bool ConfigureCalled; + protected override void Configure() + { + this.ConfigureCalled = true; + base.Configure(); + } + + public new void Start() + { + base.Start(); + } + } + + + private MyBootstrapperBase bootstrapper; + private Mock viewManager; + private Mock windowManager; + + [TestFixtureSetUp] + public void FixtureSetUp() + { + Execute.TestExecuteSynchronously = true; + AssemblySource.Assemblies.Clear(); + } + + [SetUp] + public void SetUp() + { + this.viewManager = new Mock(); + this.windowManager = new Mock(); + this.bootstrapper = new MyBootstrapperBase(this.viewManager.Object, this.windowManager.Object); + } + + [Test] + public void SetsUpOnExitHandler() + { + var ctor = typeof(ExitEventArgs).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0]; + var ea = (ExitEventArgs)ctor.Invoke(new object[] { 3 }); + //this.application.OnExit(ea); + //Assert.True(this.bootstrapper.OnExitCalled); + } + + [Test] + public void AssignsIoCGetInstanceToGetInstance() + { + IoC.GetInstance(typeof(string), null); + Assert.True(this.bootstrapper.GetInstanceCalled); + } + + [Test] + public void AssignsIoCGetAllInstancesToGetAllInstances() + { + IoC.GetAllInstances(typeof(string)); + Assert.True(this.bootstrapper.GetAllInstancesCalled); + } + + [Test] + public void AssignsIoCBuildUpToBuildUp() + { + IoC.BuildUp(new object()); + Assert.True(this.bootstrapper.BuildUpCalled); + } + + [Test] + public void StartAssignsExecuteDispatcher() + { + Execute.Dispatcher = null; + this.bootstrapper.Start(); + Assert.NotNull(Execute.Dispatcher); // Can't test any further, unfortunately + } + + [Test] + public void StartSetsUpAssemblySource() + { + AssemblySource.Assemblies.Add(null); + this.bootstrapper.Start(); + Assert.That(AssemblySource.Assemblies, Is.EquivalentTo(new[] { this.bootstrapper.GetType().Assembly })); + } + + [Test] + public void StartCallsConfigureResources() + { + this.bootstrapper.Start(); + Assert.True(this.bootstrapper.ConfigureResourcesCalled); + } + + [Test] + public void StartCallsConfigure() + { + this.bootstrapper.Start(); + Assert.True(this.bootstrapper.ConfigureCalled); + } + } +} diff --git a/StyletUnitTests/BootstrapperTests.cs b/StyletUnitTests/BootstrapperTests.cs new file mode 100644 index 0000000..443a7fb --- /dev/null +++ b/StyletUnitTests/BootstrapperTests.cs @@ -0,0 +1,136 @@ +using Moq; +using NUnit.Framework; +using Stylet; +using StyletIoC; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace StyletUnitTests +{ + [TestFixture] + public class BootstrapperTests + { + private interface I1 { } + private class C1 : I1 { } + + private class RootViewModel { } + + private class MyBootstrapper : Bootstrapper where T : class + { + public IContainer Container + { + get { return base.container; } + set { base.container = value; } + } + + public new void Configure() + { + base.Configure(); + } + + public bool ConfigureIoCCalled; + protected override void ConfigureIoC(IStyletIoCBuilder builder) + { + this.ConfigureIoCCalled = true; + builder.Bind().To(); + base.ConfigureIoC(builder); + } + + public new object GetInstance(Type service, string key) + { + return base.GetInstance(service, key); + } + + public new IEnumerable GetAllInstances(Type service) + { + return base.GetAllInstances(service); + } + + public new void BuildUp(object instance) + { + base.BuildUp(instance); + } + } + + private MyBootstrapper bootstrapper; + + [TestFixtureSetUp] + public void FixtureSetUp() + { + Execute.TestExecuteSynchronously = true; + AssemblySource.Assemblies.Clear(); + } + + [SetUp] + public void SetUp() + { + this.bootstrapper = new MyBootstrapper(); + } + + [Test] + public void ConfigureBindsRequiredTypes() + { + AssemblySource.Assemblies.Add(this.GetType().Assembly); + this.bootstrapper.Configure(); + var ioc = this.bootstrapper.Container; + + Assert.IsInstanceOf(ioc.Get()); + Assert.IsInstanceOf(ioc.Get()); + Assert.IsInstanceOf(ioc.Get()); + + // Test autobinding + Assert.DoesNotThrow(() => ioc.Get()); + } + + [Test] + public void ConfigureCallsConfigureIoCWithCorrectBuilder() + { + AssemblySource.Assemblies.Add(this.GetType().Assembly); + this.bootstrapper.Configure(); + var ioc = this.bootstrapper.Container; + + Assert.True(this.bootstrapper.ConfigureIoCCalled); + Assert.IsInstanceOf(ioc.Get()); + } + + [Test] + public void GetInstanceMappedToContainer() + { + var container = new Mock(); + this.bootstrapper.Container = container.Object; + + container.Setup(x => x.Get(typeof(string), "test")).Returns("hello").Verifiable(); + var result = this.bootstrapper.GetInstance(typeof(string), "test"); + Assert.AreEqual("hello", result); + container.Verify(); + } + + [Test] + public void GetAllInstancesMappedToContainer() + { + var container = new Mock(); + this.bootstrapper.Container = container.Object; + + container.Setup(x => x.GetAll(typeof(int), null)).Returns(new object[] { 1, 2, 3 }).Verifiable(); + var result = this.bootstrapper.GetAllInstances(typeof(int)); + Assert.That(result, Is.EquivalentTo(new[] { 1, 2, 3 })); + container.Verify(); + } + + [Test] + public void BuildUpMappedToContainer() + { + var container = new Mock(); + this.bootstrapper.Container = container.Object; + + var instance = new object(); + container.Setup(x => x.BuildUp(instance)).Verifiable(); + this.bootstrapper.BuildUp(instance); + container.Verify(); + } + } +} diff --git a/StyletUnitTests/StyletIoC/StyletIoCConstructorInjectionTests.cs b/StyletUnitTests/StyletIoC/StyletIoCConstructorInjectionTests.cs index 143ca8e..492ba97 100644 --- a/StyletUnitTests/StyletIoC/StyletIoCConstructorInjectionTests.cs +++ b/StyletUnitTests/StyletIoC/StyletIoCConstructorInjectionTests.cs @@ -9,108 +9,108 @@ using System.Threading.Tasks; namespace StyletUnitTests { - interface I1 { } - - class C1 : I1 { } - class C2 : I1 - { - public C1 C1; - public C2(C1 c1) - { - this.C1 = c1; - } - } - - class C3 - { - public C1 C1; - public C2 C2; - public C3(C1 c1, C2 c2) - { - this.C1 = c1; - this.C2 = c2; - } - } - - class C4 - { - public C1 C1; - public C4([Inject("key1")] C1 c1) - { - this.C1 = c1; - } - } - - class C5 - { - public bool RightConstructorCalled; - public C5(C1 c1, C2 c2 = null, C3 c3 = null, C4 c4 = null) - { - } - - public C5(C1 c1, C2 c2, C3 c3 = null) - { - this.RightConstructorCalled = true; - } - - public C5(C1 c1, C2 c2) - { - } - } - - class C6 - { - public bool RightConstructorCalled; - [Inject] - public C6(C1 c1) - { - this.RightConstructorCalled = true; - } - - public C6(C1 c1, C2 c2) - { - } - } - - class C7 - { - [Inject] - public C7() - { - } - - [Inject] - public C7(C1 c1) - { - } - } - - class C8 - { - public IEnumerable I1s; - public C8(IEnumerable i1s) - { - this.I1s = i1s; - } - } - - class C9 - { - public C9(I1 i1) - { - } - } - - class C10 - { - public C10(ObservableCollection c1s) - { - } - } - [TestFixture] public class StyletIoCConstructorInjectionTests { + interface I1 { } + + class C1 : I1 { } + class C2 : I1 + { + public C1 C1; + public C2(C1 c1) + { + this.C1 = c1; + } + } + + class C3 + { + public C1 C1; + public C2 C2; + public C3(C1 c1, C2 c2) + { + this.C1 = c1; + this.C2 = c2; + } + } + + class C4 + { + public C1 C1; + public C4([Inject("key1")] C1 c1) + { + this.C1 = c1; + } + } + + class C5 + { + public bool RightConstructorCalled; + public C5(C1 c1, C2 c2 = null, C3 c3 = null, C4 c4 = null) + { + } + + public C5(C1 c1, C2 c2, C3 c3 = null) + { + this.RightConstructorCalled = true; + } + + public C5(C1 c1, C2 c2) + { + } + } + + class C6 + { + public bool RightConstructorCalled; + [Inject] + public C6(C1 c1) + { + this.RightConstructorCalled = true; + } + + public C6(C1 c1, C2 c2) + { + } + } + + class C7 + { + [Inject] + public C7() + { + } + + [Inject] + public C7(C1 c1) + { + } + } + + class C8 + { + public IEnumerable I1s; + public C8(IEnumerable i1s) + { + this.I1s = i1s; + } + } + + class C9 + { + public C9(I1 i1) + { + } + } + + class C10 + { + public C10(ObservableCollection c1s) + { + } + } + [Test] public void RecursivelyPopulatesConstructorParams() { diff --git a/StyletUnitTests/StyletIoC/StyletIoCGetAllTests.cs b/StyletUnitTests/StyletIoC/StyletIoCGetAllTests.cs index 60806cd..7cf2f28 100644 --- a/StyletUnitTests/StyletIoC/StyletIoCGetAllTests.cs +++ b/StyletUnitTests/StyletIoC/StyletIoCGetAllTests.cs @@ -20,6 +20,15 @@ namespace StyletUnitTests class C21 : IC2 { } class C22 : IC2 { } + class C3 + { + public List C2s; + public C3(IEnumerable c2s) + { + this.C2s = c2s.ToList(); + } + } + // Tests that Bind() and friends worked was done in StyletIoCGetSingleTests [Test] @@ -172,5 +181,24 @@ namespace StyletUnitTests var ioc = builder.BuildContainer(); Assert.Throws(() => ioc.GetTypeOrAll(null)); } + + [Test] + public void CachedGetAllExpressionWorks() + { + // The GetAll creator's instance expression can be cached. This ensures that that works + var builder = new StyletIoCBuilder(); + builder.Bind().To(); + builder.Bind().To(); + builder.Bind().ToSelf(); + var ioc = builder.BuildContainer(); + + var c2s = ioc.GetAll().ToList(); + var c3 = ioc.Get(); + + Assert.NotNull(c3.C2s); + Assert.AreEqual(2, c3.C2s.Count); + Assert.AreNotEqual(c2s[0], c3.C2s[0]); + Assert.AreNotEqual(c2s[1], c3.C2s[1]); + } } } diff --git a/StyletUnitTests/StyletIoC/StyletIoCGetSingleTests.cs b/StyletUnitTests/StyletIoC/StyletIoCGetSingleTests.cs index 4966930..981cb8d 100644 --- a/StyletUnitTests/StyletIoC/StyletIoCGetSingleTests.cs +++ b/StyletUnitTests/StyletIoC/StyletIoCGetSingleTests.cs @@ -14,6 +14,14 @@ namespace StyletUnitTests interface IC1 { } class C1 : IC1 { } class C12 : IC1 { } + class C2 + { + public C1 C1; + public C2(C1 c1) + { + this.C1 = c1; + } + } [Test] public void SelfTransientBindingResolvesGeneric() @@ -210,5 +218,21 @@ namespace StyletUnitTests var ioc = builder.BuildContainer(); Assert.Throws(() => ioc.Get(null)); } + + [Test] + public void CachedFactoryInstanceExpressionWorks() + { + // The factory's instance expression can be cached. This ensures that that works + var builder = new StyletIoCBuilder(); + builder.Bind().ToFactory(x => new C1()); + builder.Bind().ToSelf(); + var ioc = builder.BuildContainer(); + + var c1 = ioc.Get(); + var c2 = ioc.Get(); + + Assert.NotNull(c2.C1); + Assert.AreNotEqual(c1, c2.C1); + } } } diff --git a/StyletUnitTests/StyletIoC/StyletIoCPropertyInjectionTests.cs b/StyletUnitTests/StyletIoC/StyletIoCPropertyInjectionTests.cs index b19ae84..9c63b46 100644 --- a/StyletUnitTests/StyletIoC/StyletIoCPropertyInjectionTests.cs +++ b/StyletUnitTests/StyletIoC/StyletIoCPropertyInjectionTests.cs @@ -188,5 +188,19 @@ namespace StyletUnitTests Assert.IsInstanceOf(subject.C1); } + + [Test] + public void BuildUpDoesNotReplaceAlreadySetProperties() + { + var builder = new StyletIoCBuilder(); + builder.Bind().ToSelf(); + var ioc = builder.BuildContainer(); + + var s = new Subject1(); + ioc.BuildUp(s); + var firstC1 = s.C1; + ioc.BuildUp(s); + Assert.AreEqual(s.C1, firstC1); + } } } diff --git a/StyletUnitTests/StyletIoC/StyletIoCUnboundGenericTests.cs b/StyletUnitTests/StyletIoC/StyletIoCUnboundGenericTests.cs index 95d982e..31d904b 100644 --- a/StyletUnitTests/StyletIoC/StyletIoCUnboundGenericTests.cs +++ b/StyletUnitTests/StyletIoC/StyletIoCUnboundGenericTests.cs @@ -13,6 +13,7 @@ namespace StyletUnitTests { interface I1 { } class C1 : I1 { } + class C12 { } interface I2 { } class C2 : I2 { } @@ -77,5 +78,12 @@ namespace StyletUnitTests builder.Bind(typeof(C1<>)).ToSelf(); Assert.Throws(() => builder.BuildContainer()); } + + [Test] + public void ThrowsIfUnboundGenericDoesNotImplementService() + { + var builder = new StyletIoCBuilder(); + Assert.Throws(() => builder.Bind(typeof(I1<>)).To(typeof(C12<>))); + } } } diff --git a/StyletUnitTests/StyletUnitTests.csproj b/StyletUnitTests/StyletUnitTests.csproj index ca94380..ba2a20f 100644 --- a/StyletUnitTests/StyletUnitTests.csproj +++ b/StyletUnitTests/StyletUnitTests.csproj @@ -55,6 +55,8 @@ + + diff --git a/StyletUnitTests/ViewManagerTests.cs b/StyletUnitTests/ViewManagerTests.cs index 563a837..7397b0f 100644 --- a/StyletUnitTests/ViewManagerTests.cs +++ b/StyletUnitTests/ViewManagerTests.cs @@ -67,6 +67,12 @@ namespace StyletUnitTests private ViewManager viewManager; + [TestFixtureSetUp] + public void FixtureSetUp() + { + AssemblySource.Assemblies.Clear(); + } + [SetUp] public void SetUp() { @@ -120,13 +126,13 @@ namespace StyletUnitTests } [Test] - public void LocateViewforModelThrowsIfViewNotFound() + public void LocateViewForModelThrowsIfViewNotFound() { Assert.Throws(() => this.viewManager.LocateViewForModel(typeof(C1))); } [Test] - public void LocateViewforModelFindsViewForModel() + public void LocateViewForModelFindsViewForModel() { Execute.TestExecuteSynchronously = true; AssemblySource.Assemblies.Add(Assembly.GetExecutingAssembly()); diff --git a/StyletUnitTests/WindowManagerTests.cs b/StyletUnitTests/WindowManagerTests.cs index 1acf8d6..f992e28 100644 --- a/StyletUnitTests/WindowManagerTests.cs +++ b/StyletUnitTests/WindowManagerTests.cs @@ -3,45 +3,271 @@ using NUnit.Framework; using Stylet; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Data; namespace StyletUnitTests { [TestFixture, RequiresSTA] public class WindowManagerTests { + private class MyWindowManager : WindowManager + { + public MyWindowManager(IViewManager viewManager) : base(viewManager) { } + + public new Window CreateWindow(object viewModel, bool isDialog) + { + return base.CreateWindow(viewModel, isDialog); + } + } + + private class MyWindow : Window + { + public new void OnClosing(CancelEventArgs e) + { + base.OnClosing(e); + } + + public bool OnClosedCalled; + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + this.OnClosedCalled = true; + } + + public new void OnStateChanged(EventArgs e) + { + base.OnStateChanged(e); + } + } + private Mock viewManager; - private WindowManager windowManager; + private MyWindowManager windowManager; [SetUp] public void SetUp() { this.viewManager = new Mock(); - this.windowManager = new WindowManager(); + this.windowManager = new MyWindowManager(this.viewManager.Object); IoC.GetInstance = (service, key) => this.viewManager.Object; } [Test] - public void ShowWindowAsksViewManagerForView() + public void CreateWindowAsksViewManagerForView() { var model = new object(); this.viewManager.Setup(x => x.CreateViewForModel(model)).Verifiable(); // Don't care if this throws - that's OK - try { this.windowManager.ShowWindow(model); } + try { this.windowManager.CreateWindow(model, false); } catch (Exception) { } this.viewManager.VerifyAll(); } [Test] - public void ShowWindowThrowsIfViewIsntAWindow() + public void CreateWindowThrowsIfViewIsntAWindow() { var model = new object(); this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(new UIElement()); - Assert.Throws(() => this.windowManager.ShowWindow(model)); + Assert.Throws(() => this.windowManager.CreateWindow(model, false)); + } + + [Test] + public void CreateWindowBindsViewToModel() + { + var model = new object(); + var window = new Window(); + this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window); + + this.windowManager.CreateWindow(model, false); + + this.viewManager.Verify(x => x.BindViewToModel(window, model)); + } + + [Test] + public void CreateWindowSetsUpTitleBindingIfViewModelIsIHaveDisplayName() + { + var model = new Screen(); + var window = new Window(); + this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window); + + this.windowManager.CreateWindow(model, false); + + var e = window.GetBindingExpression(Window.TitleProperty); + Assert.AreEqual(BindingMode.TwoWay, e.ParentBinding.Mode); + Assert.AreEqual("DisplayName", e.ParentBinding.Path.Path); + } + + [Test] + public void CreateWindowActivatesViewModel() + { + var model = new Mock(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(new Window()); + this.windowManager.CreateWindow(model.Object, false); + model.Verify(x => x.Activate()); + } + + [Test] + public void WindowStateChangedActivatesIfMaximized() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + window.WindowState = WindowState.Maximized; + window.OnStateChanged(EventArgs.Empty); + model.Verify(x => x.Activate()); + } + + [Test] + public void WindowStateChangedActivatesIfNormal() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + window.WindowState = WindowState.Normal; + window.OnStateChanged(EventArgs.Empty); + model.Verify(x => x.Activate()); + } + + [Test] + public void WindowStateChangedDeactivatesIfMinimized() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + window.WindowState = WindowState.Minimized; + window.OnStateChanged(EventArgs.Empty); + model.Verify(x => x.Deactivate()); + } + + [Test] + public void WindowClosingDoesNothingIfAlreadyCancelled() + { + var model = new Screen(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window); + this.windowManager.CreateWindow(model, false); + window.OnClosing(new CancelEventArgs(true)); + } + + [Test] + public void WindowClosingCancelsIfCanCloseAsyncReturnsSynchronousFalse() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + model.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(false)); + var ea = new CancelEventArgs(); + window.OnClosing(ea); + Assert.True(ea.Cancel); + } + + [Test] + public void WindowClosingDoesNotCancelIfCanCloseAsyncReturnsSynchronousTrue() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + model.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true)); + var ea = new CancelEventArgs(); + window.OnClosing(ea); + Assert.False(ea.Cancel); + } + + [Test] + public void WindowClosingCancelsIfCanCloseAsyncReturnsAsynchronousFalse() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + model.Setup(x => x.CanCloseAsync()).Returns(Task.Delay(1).ContinueWith(t => false)); + var ea = new CancelEventArgs(); + window.OnClosing(ea); + Assert.True(ea.Cancel); + } + + [Test] + public void WindowClosingCancelsIfCanCloseAsyncReturnsAsynchronousTrue() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + model.Setup(x => x.CanCloseAsync()).Returns(Task.Delay(1).ContinueWith(t => true)); + var ea = new CancelEventArgs(); + window.OnClosing(ea); + Assert.True(ea.Cancel); + } + + [Test] + public void WindowClosingClosesWindowIfCanCloseAsyncCompletesTrue() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + this.windowManager.CreateWindow(model.Object, false); + var tcs = new TaskCompletionSource(); + model.Setup(x => x.CanCloseAsync()).Returns(tcs.Task); + window.OnClosing(new CancelEventArgs()); + model.Verify(x => x.Close(), Times.Never); + tcs.SetResult(true); + model.Verify(x => x.Close(), Times.Once); + + Assert.True(window.OnClosedCalled); + + // Check it didn't call WindowClosing again - just the first time + model.Verify(x => x.CanCloseAsync(), Times.Once); + } + + [Test] + public void CloseItemDoesNothingIfItemIsWrong() + { + var model = new Screen(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window); + this.windowManager.CreateWindow(model, false); + ((IChildDelegate)model.Parent).CloseItem(new object()); + } + + [Test] + public void CloseItemDoesNothingIfCanCloseReturnsFalse() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + object parent = null; + model.SetupSet(x => x.Parent = It.IsAny()).Callback((object x) => parent = x); + this.windowManager.CreateWindow(model.Object, false); + + model.Setup(x => x.CanCloseAsync()).Returns(Task.Delay(1).ContinueWith(t => false)); + ((IChildDelegate)parent).CloseItem(model.Object); + } + + [Test] + public void CloseItemClosesAndWindowViewModelIfCanCloseReturnsTrue() + { + var model = new Mock(); + var window = new MyWindow(); + this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window); + object parent = null; + model.SetupSet(x => x.Parent = It.IsAny()).Callback((object x) => parent = x); + this.windowManager.CreateWindow(model.Object, true); + + model.Setup(x => x.CanCloseAsync()).ReturnsAsync(true); + ((IChildDelegate)parent).CloseItem(model.Object); + + model.Verify(x => x.Close()); + Assert.True(window.OnClosedCalled); } } }