diff --git a/Stylet/ConductorOneActive.cs b/Stylet/ConductorOneActive.cs index 37f58f4..f8d8da7 100644 --- a/Stylet/ConductorOneActive.cs +++ b/Stylet/ConductorOneActive.cs @@ -142,6 +142,8 @@ namespace Stylet { foreach (var item in this.items.OfType()) item.Deactivate(true); + + this.items.Clear(); } else { diff --git a/Stylet/PropertyChangedBase.cs b/Stylet/PropertyChangedBase.cs index db15725..14a6254 100644 --- a/Stylet/PropertyChangedBase.cs +++ b/Stylet/PropertyChangedBase.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Stylet { - public class PropertyChangedBase : INotifyPropertyChanged, INotifyPropertyChangedDispatcher + public abstract class PropertyChangedBase : INotifyPropertyChanged, INotifyPropertyChangedDispatcher { private Action _propertyChangedDispatcher = Execute.DefaultPropertyChangedDispatcher; /// diff --git a/Stylet/Screen.cs b/Stylet/Screen.cs index b0083b0..a3736db 100644 --- a/Stylet/Screen.cs +++ b/Stylet/Screen.cs @@ -86,7 +86,7 @@ namespace Stylet void IDeactivate.Deactivate(bool close) { - if (!this.IsActive) + if (!this.IsActive && !close) return; this.IsActive = false; diff --git a/StyletUnitTests/ConductorOneActiveTests.cs b/StyletUnitTests/ConductorOneActiveTests.cs index 71de1d8..6130382 100644 --- a/StyletUnitTests/ConductorOneActiveTests.cs +++ b/StyletUnitTests/ConductorOneActiveTests.cs @@ -90,5 +90,126 @@ namespace StyletUnitTests Assert.AreEqual(screen2.Object, this.conductor.ActiveItem); Assert.AreEqual(new[] { screen1.Object, screen2.Object }, this.conductor.Items); } + + [Test] + public void ActivateItemDoesNotActivateIfConductorIsNotActive() + { + var screen = new Mock(); + this.conductor.ActivateItem(screen.Object); + screen.Verify(x => x.Activate(), Times.Never); + } + + [Test] + public void ActivateItemDoesActiveIfConductorIsActive() + { + var screen = new Mock(); + ((IActivate)this.conductor).Activate(); + this.conductor.ActivateItem(screen.Object); + screen.Verify(x => x.Activate()); + } + + [Test] + public void DeactiveDeactivatesItems() + { + var screen = new Mock(); + ((IActivate)this.conductor).Activate(); + this.conductor.ActivateItem(screen.Object); + ((IDeactivate)this.conductor).Deactivate(false); + screen.Verify(x => x.Deactivate(false)); + } + + [Test] + public void ClosingClosesAllItems() + { + var screen = new Mock(); + ((IActivate)this.conductor).Activate(); + this.conductor.ActivateItem(screen.Object); + ((IDeactivate)this.conductor).Deactivate(true); + screen.Verify(x => x.Deactivate(true)); + Assert.AreEqual(0, this.conductor.Items.Count); + } + + [Test] + public void ConductorCanCloseIfAllItemsCanClose() + { + var screen1 = new Mock(); + var screen2 = new Mock(); + + screen1.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true)); + screen2.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true)); + + this.conductor.Items.AddRange(new[] { screen1.Object, screen2.Object }); + Assert.IsTrue(this.conductor.CanCloseAsync().Result); + } + + [Test] + public void ConductorCanNotCloseIfAnyItemCanNotClose() + { + var screen1 = new Mock(); + var screen2 = new Mock(); + + screen1.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true)); + screen2.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(false)); + + this.conductor.Items.AddRange(new[] { screen1.Object, screen2.Object }); + Assert.IsFalse(this.conductor.CanCloseAsync().Result); + } + + [Test] + public void AddingItemSetsParent() + { + var screen = new Mock(); + this.conductor.Items.Add(screen.Object); + screen.VerifySet(x => x.Parent = this.conductor); + } + + [Test] + public void RemovingItemRemovesParent() + { + var screen = new Mock(); + this.conductor.Items.Add(screen.Object); + this.conductor.Items.Remove(screen.Object); + screen.VerifySet(x => x.Parent = null); + } + + [Test] + public void AddingItemTwiceDoesNotResultInDuplicates() + { + var screen = new Mock(); + this.conductor.ActivateItem(screen.Object); + this.conductor.ActivateItem(screen.Object); + Assert.AreEqual(1, this.conductor.Items.Count); + } + + [Test] + public void DeactivateItemDeactivatesItemButDoesNotRemoveFromItems() + { + var screen = new Mock(); + this.conductor.ActivateItem(screen.Object); + this.conductor.DeactivateItem(screen.Object, false); + screen.Verify(x => x.Deactivate(false)); + Assert.That(this.conductor.Items, Is.EquivalentTo(new[] { screen.Object })); + } + + [Test] + public void CloseItemDoesNothingIfItemCanNotClose() + { + var screen = new Mock(); + this.conductor.ActivateItem(screen.Object); + this.conductor.DeactivateItem(screen.Object, true); + screen.Verify(x => x.Deactivate(true), Times.Never); + Assert.That(this.conductor.Items, Is.EquivalentTo(new[] { screen.Object })); + } + + [Test] + public void CloseItemDeactivatesItemAndRemovesFromItemsIfItemCanClose() + { + var screen = new Mock(); + screen.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true)); + this.conductor.ActivateItem(screen.Object); + this.conductor.DeactivateItem(screen.Object, true); + screen.Verify(x => x.Deactivate(true)); + Assert.AreEqual(0, this.conductor.Items.Count); + } } } diff --git a/StyletUnitTests/ExecuteTests.cs b/StyletUnitTests/ExecuteTests.cs new file mode 100644 index 0000000..381032c --- /dev/null +++ b/StyletUnitTests/ExecuteTests.cs @@ -0,0 +1,87 @@ +using Moq; +using NUnit.Framework; +using Stylet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace StyletUnitTests +{ + [TestFixture] + public class ExecuteTests + { + [Test] + public void OnUIThreadExecutesSynchronouslyIfNoSynchronizationContext() + { + Execute.SynchronizationContext = null; + bool blockWasRun = false; + Execute.OnUIThread(() => blockWasRun = true); + Assert.IsTrue(blockWasRun); + } + + [Test] + public void OnUIThreadExecutesAsynchronouslyIfSynchronizationContextIsNotNull() + { + var sync = new Mock(); + Execute.SynchronizationContext = sync.Object; + + SendOrPostCallback passedAction = null; + sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + + bool actionCalled = false; + Execute.OnUIThread(() => actionCalled = true); + + Assert.IsFalse(actionCalled); + passedAction(null); + Assert.IsTrue(actionCalled); + } + + [Test] + public async Task OnUIThreadAsyncExecutesSynchronouslyIfNoSynchronizationContext() + { + Execute.SynchronizationContext = null; + bool blockWasRun = false; + await Execute.OnUIThreadAsync(() => blockWasRun = true); + Assert.IsTrue(blockWasRun); + } + + [Test] + public void OnUIThreadAsyncExecutesAsynchronouslyIfSynchronizationContextIsNotNull() + { + var sync = new Mock(); + Execute.SynchronizationContext = sync.Object; + + SendOrPostCallback passedAction = null; + sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + + bool actionCalled = false; + var task = Execute.OnUIThreadAsync(() => actionCalled = true); + + Assert.IsFalse(task.IsCompleted); + Assert.IsFalse(actionCalled); + passedAction(null); + Assert.IsTrue(actionCalled); + Assert.IsTrue(task.IsCompleted); + } + + [Test] + public void OnUIThreadAsyncPropagatesException() + { + var sync = new Mock(); + Execute.SynchronizationContext = sync.Object; + + SendOrPostCallback passedAction = null; + sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + + var ex = new Exception("test"); + var task = Execute.OnUIThreadAsync(() => { throw ex; }); + + passedAction(null); + Assert.IsTrue(task.IsFaulted); + Assert.AreEqual(ex, task.Exception.InnerExceptions[0]); + } + } +} diff --git a/StyletUnitTests/PropertyChangedBaseTests.cs b/StyletUnitTests/PropertyChangedBaseTests.cs new file mode 100644 index 0000000..c6ad117 --- /dev/null +++ b/StyletUnitTests/PropertyChangedBaseTests.cs @@ -0,0 +1,129 @@ +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 PropertyChangedBaseTests + { + class PropertyChanged : PropertyChangedBase + { + public int IntProperty { get; set; } + public string StringProperty + { + set { this.NotifyOfPropertyChange(); } + } + private double _doubleProperty; + public double DoubleProperty + { + get { return this._doubleProperty; } + set { SetAndNotify(ref this._doubleProperty, value); } + } + public void RaiseIntPropertyChangedWithExpression() + { + this.NotifyOfPropertyChange(() => this.IntProperty); + } + public void RaiseIntPropertyChangedWithString() + { + this.NotifyOfPropertyChange("IntProperty"); + } + } + + [Test] + public void RefreshRaisesPropertyChangedWithEmptyString() + { + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + pc.Refresh(); + Assert.AreEqual(String.Empty, changedProperty); + } + + [Test] + public void NotifyOfPropertyChangedWithExpressionRaises() + { + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + pc.RaiseIntPropertyChangedWithExpression(); + Assert.AreEqual("IntProperty", changedProperty); + } + + [Test] + public void NotifyOfPropertyChangedWithStringRaises() + { + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + pc.RaiseIntPropertyChangedWithString(); + Assert.AreEqual("IntProperty", changedProperty); + } + + [Test] + public void NotifyOfPropertyChangedWithCallerMemberName() + { + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + pc.StringProperty = "hello"; + Assert.AreEqual("StringProperty", changedProperty); + } + + [Test] + public void UsesDispatcher() + { + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + + Action action = null; + pc.PropertyChangedDispatcher = a => action = a; + + pc.RaiseIntPropertyChangedWithExpression(); + Assert.IsNull(changedProperty); + Assert.IsNotNull(action); + + action(); + Assert.AreEqual("IntProperty", changedProperty); + } + + [Test] + public void UsesExecutesDispatcherByDefault() + { + Action action = null; + var oldDispatcher = Execute.DefaultPropertyChangedDispatcher; + Execute.DefaultPropertyChangedDispatcher = a => action = a; + + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + + pc.RaiseIntPropertyChangedWithExpression(); + Assert.IsNull(changedProperty); + Assert.IsNotNull(action); + + action(); + Assert.AreEqual("IntProperty", changedProperty); + + Execute.DefaultPropertyChangedDispatcher = oldDispatcher; + } + + [Test] + public void SetAndNotifyWorks() + { + var pc = new PropertyChanged(); + string changedProperty = null; + pc.PropertyChanged += (o, e) => changedProperty = e.PropertyName; + + pc.DoubleProperty = 5; + + Assert.AreEqual("DoubleProperty", changedProperty); + Assert.AreEqual(5, pc.DoubleProperty); + } + } +} diff --git a/StyletUnitTests/StyletUnitTests.csproj b/StyletUnitTests/StyletUnitTests.csproj index a300128..62f5669 100644 --- a/StyletUnitTests/StyletUnitTests.csproj +++ b/StyletUnitTests/StyletUnitTests.csproj @@ -57,7 +57,9 @@ + +