From af9f743c9ef9ba1e78679ff0d7f631625c262330 Mon Sep 17 00:00:00 2001 From: Niclas Kristek Date: Wed, 21 Aug 2019 21:12:53 +0200 Subject: [PATCH] Rename ValidatingViewModel to ValidatingBindable - ViewModel now inherits ValidatingBindable instead of Bindable and thus conforms to INotifyDataErrorInfo - SetErrors is now protected - IsValid was moved to ViewModel --- src/Smaragd/ViewModels/DialogModel.cs | 2 +- src/Smaragd/ViewModels/IBindable.cs | 1 + src/Smaragd/ViewModels/IValidatingBindable.cs | 13 + .../ViewModels/IValidatingViewModel.cs | 25 -- src/Smaragd/ViewModels/IViewModel.cs | 7 +- ...tingViewModel.cs => ValidatingBindable.cs} | 49 ++- src/Smaragd/ViewModels/ViewModel.cs | 11 +- .../ViewModels/ValidatingBindableTests.cs | 207 +++++++++++++ .../ViewModels/ValidatingViewModelTests.cs | 281 ------------------ .../ViewModels/ViewModelTests.cs | 74 +++++ 10 files changed, 331 insertions(+), 339 deletions(-) create mode 100644 src/Smaragd/ViewModels/IValidatingBindable.cs delete mode 100644 src/Smaragd/ViewModels/IValidatingViewModel.cs rename src/Smaragd/ViewModels/{ValidatingViewModel.cs => ValidatingBindable.cs} (79%) create mode 100644 test/Smaragd.Tests/ViewModels/ValidatingBindableTests.cs delete mode 100644 test/Smaragd.Tests/ViewModels/ValidatingViewModelTests.cs diff --git a/src/Smaragd/ViewModels/DialogModel.cs b/src/Smaragd/ViewModels/DialogModel.cs index 489627a..6ec8c3f 100644 --- a/src/Smaragd/ViewModels/DialogModel.cs +++ b/src/Smaragd/ViewModels/DialogModel.cs @@ -2,7 +2,7 @@ { /// public abstract class DialogModel - : ValidatingViewModel, IDialogModel + : ViewModel, IDialogModel { private string _title; diff --git a/src/Smaragd/ViewModels/IBindable.cs b/src/Smaragd/ViewModels/IBindable.cs index f2e4c4e..adaab8d 100644 --- a/src/Smaragd/ViewModels/IBindable.cs +++ b/src/Smaragd/ViewModels/IBindable.cs @@ -2,6 +2,7 @@ namespace NKristek.Smaragd.ViewModels { + /// /// /// Notifies clients that a property value is changing or has changed. /// diff --git a/src/Smaragd/ViewModels/IValidatingBindable.cs b/src/Smaragd/ViewModels/IValidatingBindable.cs new file mode 100644 index 0000000..ba43508 --- /dev/null +++ b/src/Smaragd/ViewModels/IValidatingBindable.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace NKristek.Smaragd.ViewModels +{ + /// + /// + /// Notifies clients that errors of a property have changed. + /// + public interface IValidatingBindable + : IBindable, INotifyDataErrorInfo + { + } +} diff --git a/src/Smaragd/ViewModels/IValidatingViewModel.cs b/src/Smaragd/ViewModels/IValidatingViewModel.cs deleted file mode 100644 index 4a5caff..0000000 --- a/src/Smaragd/ViewModels/IValidatingViewModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections; -using System.ComponentModel; - -namespace NKristek.Smaragd.ViewModels -{ - /// - /// - /// Defines a which performs validation. - /// - public interface IValidatingViewModel - : IViewModel, INotifyDataErrorInfo - { - /// - /// If data is valid. - /// - bool IsValid { get; } - - /// - /// Set validation errors of a property. - /// - /// The errors of the property. - /// The name of the property. - void SetErrors(IEnumerable errors, string propertyName = null); - } -} \ No newline at end of file diff --git a/src/Smaragd/ViewModels/IViewModel.cs b/src/Smaragd/ViewModels/IViewModel.cs index a189f10..238b105 100644 --- a/src/Smaragd/ViewModels/IViewModel.cs +++ b/src/Smaragd/ViewModels/IViewModel.cs @@ -5,7 +5,7 @@ /// Defines properties which are useful for a viewmodel implementation. /// public interface IViewModel - : IBindable + : IValidatingBindable { /// /// Indicates if a property changed and the change is not persisted. @@ -26,5 +26,10 @@ public interface IViewModel /// Indicates if this instance is currently being updated. /// bool IsUpdating { get; set; } + + /// + /// If data is valid. + /// + bool IsValid { get; } } } \ No newline at end of file diff --git a/src/Smaragd/ViewModels/ValidatingViewModel.cs b/src/Smaragd/ViewModels/ValidatingBindable.cs similarity index 79% rename from src/Smaragd/ViewModels/ValidatingViewModel.cs rename to src/Smaragd/ViewModels/ValidatingBindable.cs index 9ff87b9..16dd91a 100644 --- a/src/Smaragd/ViewModels/ValidatingViewModel.cs +++ b/src/Smaragd/ViewModels/ValidatingBindable.cs @@ -4,26 +4,33 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; -using NKristek.Smaragd.Attributes; namespace NKristek.Smaragd.ViewModels { - /// - public abstract class ValidatingViewModel - : ViewModel, IValidatingViewModel + /// + public abstract class ValidatingBindable + : Bindable, IValidatingBindable { private readonly Dictionary> _errors = new Dictionary>(); - #region IValidatingViewModel - /// - [IsDirtyIgnored] - [PropertySource(nameof(HasErrors))] - public virtual bool IsValid => !HasErrors; + public virtual bool HasErrors => _errors.Count > 0; /// + public virtual IEnumerable GetErrors(string propertyName) + { + if (String.IsNullOrEmpty(propertyName)) + return _errors.SelectMany(kvp => kvp.Value); + return _errors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty(); + } + + /// + /// Set validation errors of a property. + /// + /// The errors of the property. + /// The name of the property. /// is or empty. - public virtual void SetErrors(IEnumerable errors, [CallerMemberName] string propertyName = null) + protected virtual void SetErrors(IEnumerable errors, [CallerMemberName] string propertyName = null) { if (String.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(nameof(propertyName)); @@ -44,25 +51,9 @@ public virtual void SetErrors(IEnumerable errors, [CallerMemberName] string prop } } - #endregion - - #region INotifyDataErrorInfo - - /// - [IsDirtyIgnored] - public virtual bool HasErrors => _errors.Count > 0; - - /// - public virtual IEnumerable GetErrors(string propertyName) - { - if (String.IsNullOrEmpty(propertyName)) - return _errors.SelectMany(kvp => kvp.Value); - return _errors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty(); - } - /// public event EventHandler ErrorsChanged; - + /// /// Raises an event on to indicate that the validation errors have changed. /// @@ -71,7 +62,5 @@ protected virtual void NotifyErrorsChanged([CallerMemberName] string propertyNam { ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Smaragd/ViewModels/ViewModel.cs b/src/Smaragd/ViewModels/ViewModel.cs index c2cfa0e..ca58b6e 100644 --- a/src/Smaragd/ViewModels/ViewModel.cs +++ b/src/Smaragd/ViewModels/ViewModel.cs @@ -12,7 +12,7 @@ namespace NKristek.Smaragd.ViewModels { /// public abstract class ViewModel - : Bindable, IViewModel + : ValidatingBindable, IViewModel { private readonly INotificationCache _notificationCache = new NotificationCache(); @@ -127,6 +127,15 @@ private void OnChildCollectionChanged(object sender, NotifyCollectionChangedEven IsDirty = true; } + /// + [IsDirtyIgnored] + public override bool HasErrors => base.HasErrors; + + /// + [IsDirtyIgnored] + [PropertySource(nameof(HasErrors))] + public virtual bool IsValid => !HasErrors; + private bool _isDirty; /// diff --git a/test/Smaragd.Tests/ViewModels/ValidatingBindableTests.cs b/test/Smaragd.Tests/ViewModels/ValidatingBindableTests.cs new file mode 100644 index 0000000..808b062 --- /dev/null +++ b/test/Smaragd.Tests/ViewModels/ValidatingBindableTests.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using NKristek.Smaragd.ViewModels; +using Xunit; + +namespace NKristek.Smaragd.Tests.ViewModels +{ + public class ValidatingBindableTests + { + private class TestBindable + : ValidatingBindable + { + private int _property; + + public int Property + { + get => _property; + set => SetProperty(ref _property, value); + } + + private int _anotherProperty; + + public int AnotherProperty + { + get => _anotherProperty; + set => SetProperty(ref _anotherProperty, value); + } + + public void NotifyPropertyChangingExternal(string propertyName) + { + NotifyPropertyChanging(propertyName); + } + + public void NotifyPropertyChangedExternal(string propertyName) + { + NotifyPropertyChanged(propertyName); + } + + public bool HasNotifiedErrorsChanged; + + protected override void NotifyErrorsChanged([CallerMemberName] string propertyName = null) + { + base.NotifyErrorsChanged(propertyName); + HasNotifiedErrorsChanged = true; + } + + public void NotifyErrorsChangedExternal(string propertyName) + { + NotifyErrorsChanged(propertyName); + } + + public void SetErrorsExternal(IEnumerable errors, [CallerMemberName] string propertyName = null) + { + SetErrors(errors, propertyName); + } + } + + #region SetErrors + + [Fact] + public void SetErrors_throws_ArgumentNullException_when_propertyName_is_null() + { + var viewModel = new TestBindable(); + Assert.Throws(() => viewModel.SetErrorsExternal(Enumerable.Empty(), null)); + } + + [Fact] + public void SetErrors_propertyName_is_set_by_CallerMemberName() + { + var invokedErrorChangedEvents = new List(); + var viewModel = new TestBindable(); + viewModel.ErrorsChanged += (sender, args) => invokedErrorChangedEvents.Add(args.PropertyName); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1)); + Assert.Contains(nameof(SetErrors_propertyName_is_set_by_CallerMemberName), invokedErrorChangedEvents); + } + + [Fact] + public void SetErrors_sets_errors_of_property() + { + var errors = Enumerable.Repeat("error", 1); + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(errors, nameof(viewModel.Property)); + Assert.Equal(errors, viewModel.GetErrors(nameof(viewModel.Property))); + } + + [Fact] + public void SetErrors_null_removes_errors_of_property() + { + var errors = Enumerable.Repeat("error", 1); + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(errors, nameof(viewModel.Property)); + viewModel.SetErrorsExternal(null, nameof(viewModel.Property)); + Assert.Empty(viewModel.GetErrors(nameof(viewModel.Property))); + } + + [Fact] + public void SetErrors_empty_collection_removes_errors_of_property() + { + var errors = Enumerable.Repeat("error", 1); + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(errors, nameof(viewModel.Property)); + viewModel.SetErrorsExternal(Enumerable.Empty(), nameof(viewModel.Property)); + Assert.Empty(viewModel.GetErrors(nameof(viewModel.Property))); + } + + [Fact] + public void SetErrors_notifies_when_errors_changed() + { + var viewModel = new TestBindable(); + Assert.False(viewModel.HasNotifiedErrorsChanged); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + Assert.True(viewModel.HasNotifiedErrorsChanged); + } + + #endregion + + #region HasErrors + + [Fact] + public void HasErrors_is_true_when_validation_errors_exist() + { + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + Assert.True(viewModel.HasErrors); + } + + [Fact] + public void HasErrors_is_false_when_no_validation_errors_exist() + { + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + viewModel.SetErrorsExternal(Enumerable.Empty(), nameof(viewModel.Property)); + Assert.False(viewModel.HasErrors); + } + + [Fact] + public void HasErrors_gets_notified_before_errors_change() + { + var invokedPropertyChangingEvents = new List(); + var viewModel = new TestBindable(); + viewModel.PropertyChanging += (sender, args) => invokedPropertyChangingEvents.Add(args.PropertyName); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + Assert.Contains(nameof(viewModel.HasErrors), invokedPropertyChangingEvents); + } + + [Fact] + public void HasErrors_gets_notified_after_errors_change() + { + var invokedPropertyChangedEvents = new List(); + var viewModel = new TestBindable(); + viewModel.PropertyChanged += (sender, args) => invokedPropertyChangedEvents.Add(args.PropertyName); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + Assert.Contains(nameof(viewModel.HasErrors), invokedPropertyChangedEvents); + } + + #endregion + + #region GetErrors + + [Theory] + [InlineData(null, 2)] + [InlineData("", 2)] + [InlineData(nameof(TestBindable.Property), 1)] + [InlineData("NotExistingProperty", 0)] + public void GetErrors_with_error(string propertyName, int expectedErrorCount) + { + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestBindable.Property)); + viewModel.SetErrorsExternal(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestBindable.AnotherProperty)); + Assert.Equal(Enumerable.Repeat("Value has to be at least 5.", expectedErrorCount), viewModel.GetErrors(propertyName)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(nameof(TestBindable.Property))] + [InlineData("NotExistingProperty")] + public void GetErrors_without_error(string propertyName) + { + var viewModel = new TestBindable(); + viewModel.SetErrorsExternal(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestBindable.Property)); + viewModel.SetErrorsExternal(Enumerable.Empty(), nameof(viewModel.Property)); + Assert.Empty(viewModel.GetErrors(propertyName)); + } + + #endregion + + #region NotifyErrorsChanged + + [Fact] + public void NotifyErrorsChanged_raises_event_on_ErrorsChanged() + { + var invokedErrorChangedEvents = new List(); + var viewModel = new TestBindable(); + viewModel.ErrorsChanged += (sender, args) => invokedErrorChangedEvents.Add(args.PropertyName); + + viewModel.NotifyErrorsChangedExternal(nameof(viewModel.Property)); + + Assert.Equal(Enumerable.Repeat(nameof(viewModel.Property), 1), invokedErrorChangedEvents); + } + + #endregion + } +} \ No newline at end of file diff --git a/test/Smaragd.Tests/ViewModels/ValidatingViewModelTests.cs b/test/Smaragd.Tests/ViewModels/ValidatingViewModelTests.cs deleted file mode 100644 index 449b2aa..0000000 --- a/test/Smaragd.Tests/ViewModels/ValidatingViewModelTests.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using NKristek.Smaragd.ViewModels; -using Xunit; - -namespace NKristek.Smaragd.Tests.ViewModels -{ - public class ValidatingViewModelTests - { - private class TestViewModel - : ValidatingViewModel - { - private int _property; - - public int Property - { - get => _property; - set => SetProperty(ref _property, value); - } - - private int _anotherProperty; - - public int AnotherProperty - { - get => _anotherProperty; - set => SetProperty(ref _anotherProperty, value); - } - - public void NotifyPropertyChangingExternal(string propertyName) - { - NotifyPropertyChanging(propertyName); - } - - public void NotifyPropertyChangedExternal(string propertyName) - { - NotifyPropertyChanged(propertyName); - } - - public bool HasNotifiedErrorsChanged; - - protected override void NotifyErrorsChanged([CallerMemberName] string propertyName = null) - { - base.NotifyErrorsChanged(propertyName); - HasNotifiedErrorsChanged = true; - } - - public void NotifyErrorsChangedExternal(string propertyName) - { - NotifyErrorsChanged(propertyName); - } - } - - #region IsValid - - [Fact] - public void IsValid_is_false_when_validation_errors_exist() - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); - Assert.False(viewModel.IsValid); - } - - [Fact] - public void IsValid_is_true_when_no_validation_errors_exist() - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); - viewModel.SetErrors(Enumerable.Empty(), nameof(viewModel.Property)); - Assert.True(viewModel.IsValid); - } - - [Fact] - public void IsValid_does_not_set_IsDirty() - { - var viewModel = new TestViewModel(); - viewModel.NotifyPropertyChangedExternal(nameof(viewModel.IsValid)); - Assert.False(viewModel.IsDirty); - } - - [Fact] - public void IsValid_gets_notified_by_HasErrors() - { - var invokedPropertyChangingEvents = new List(); - var invokedPropertyChangedEvents = new List(); - var viewModel = new TestViewModel(); - viewModel.PropertyChanging += (sender, args) => invokedPropertyChangingEvents.Add(args.PropertyName); - viewModel.PropertyChanged += (sender, args) => invokedPropertyChangedEvents.Add(args.PropertyName); - - viewModel.NotifyPropertyChangingExternal(nameof(viewModel.HasErrors)); - viewModel.NotifyPropertyChangedExternal(nameof(viewModel.HasErrors)); - - Assert.Contains(nameof(viewModel.IsValid), invokedPropertyChangingEvents); - Assert.Contains(nameof(viewModel.IsValid), invokedPropertyChangedEvents); - } - - #endregion - - #region SetErrors - - [Fact] - public void SetErrors_throws_ArgumentNullException_when_propertyName_is_null() - { - var viewModel = new TestViewModel(); - Assert.Throws(() => viewModel.SetErrors(Enumerable.Empty(), null)); - } - - [Fact] - public void SetErrors_propertyName_is_set_by_CallerMemberName() - { - var invokedErrorChangedEvents = new List(); - var viewModel = new TestViewModel(); - viewModel.ErrorsChanged += (sender, args) => invokedErrorChangedEvents.Add(args.PropertyName); - viewModel.SetErrors(Enumerable.Repeat("error", 1)); - Assert.Contains(nameof(SetErrors_propertyName_is_set_by_CallerMemberName), invokedErrorChangedEvents); - } - - [Fact] - public void SetErrors_sets_errors_of_property() - { - var errors = Enumerable.Repeat("error", 1); - var viewModel = new TestViewModel(); - viewModel.SetErrors(errors, nameof(viewModel.Property)); - Assert.Equal(errors, viewModel.GetErrors(nameof(viewModel.Property))); - } - - [Fact] - public void SetErrors_null_removes_errors_of_property() - { - var errors = Enumerable.Repeat("error", 1); - var viewModel = new TestViewModel(); - viewModel.SetErrors(errors, nameof(viewModel.Property)); - viewModel.SetErrors(null, nameof(viewModel.Property)); - Assert.Empty(viewModel.GetErrors(nameof(viewModel.Property))); - } - - [Fact] - public void SetErrors_empty_collection_removes_errors_of_property() - { - var errors = Enumerable.Repeat("error", 1); - var viewModel = new TestViewModel(); - viewModel.SetErrors(errors, nameof(viewModel.Property)); - viewModel.SetErrors(Enumerable.Empty(), nameof(viewModel.Property)); - Assert.Empty(viewModel.GetErrors(nameof(viewModel.Property))); - } - - [Fact] - public void SetErrors_notifies_when_errors_changed() - { - var viewModel = new TestViewModel(); - Assert.False(viewModel.HasNotifiedErrorsChanged); - viewModel.SetErrors(Enumerable.Repeat("error", 1)); - Assert.True(viewModel.HasNotifiedErrorsChanged); - } - - #endregion - - #region HasErrors - - [Fact] - public void HasErrors_is_true_when_validation_errors_exist() - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); - Assert.True(viewModel.HasErrors); - } - - [Fact] - public void HasErrors_is_false_when_no_validation_errors_exist() - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); - viewModel.SetErrors(Enumerable.Empty(), nameof(viewModel.Property)); - Assert.False(viewModel.HasErrors); - } - - [Fact] - public void HasErrors_does_not_set_IsDirty() - { - var viewModel = new TestViewModel(); - viewModel.NotifyPropertyChangedExternal(nameof(viewModel.HasErrors)); - Assert.False(viewModel.IsDirty); - } - - [Fact] - public void HasErrors_gets_notified_before_errors_change() - { - var invokedPropertyChangingEvents = new List(); - var viewModel = new TestViewModel(); - viewModel.PropertyChanging += (sender, args) => invokedPropertyChangingEvents.Add(args.PropertyName); - viewModel.SetErrors(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); - Assert.Contains(nameof(viewModel.HasErrors), invokedPropertyChangingEvents); - } - - [Fact] - public void HasErrors_gets_notified_after_errors_change() - { - var invokedPropertyChangedEvents = new List(); - var viewModel = new TestViewModel(); - viewModel.PropertyChanged += (sender, args) => invokedPropertyChangedEvents.Add(args.PropertyName); - viewModel.SetErrors(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); - Assert.Contains(nameof(viewModel.HasErrors), invokedPropertyChangedEvents); - } - - #endregion - - #region GetErrors - - [Theory] - [InlineData(null, 2)] - [InlineData("", 2)] - [InlineData(nameof(TestViewModel.Property), 1)] - [InlineData("NotExistingProperty", 0)] - public void GetErrors_with_error(string propertyName, int expectedErrorCount) - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestViewModel.Property)); - viewModel.SetErrors(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestViewModel.AnotherProperty)); - Assert.Equal(Enumerable.Repeat("Value has to be at least 5.", expectedErrorCount), viewModel.GetErrors(propertyName)); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(nameof(TestViewModel.Property))] - [InlineData("NotExistingProperty")] - public void GetErrors_without_error(string propertyName) - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestViewModel.Property)); - viewModel.SetErrors(Enumerable.Empty(), nameof(viewModel.Property)); - Assert.Empty(viewModel.GetErrors(propertyName)); - } - - [Theory] - [InlineData(null, 2)] - [InlineData("", 2)] - [InlineData(nameof(TestViewModel.Property), 1)] - [InlineData("NotExistingProperty", 0)] - public void INotifyDataErrorInfoGetErrors_with_error(string propertyName, int expectedErrorCount) - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestViewModel.Property)); - viewModel.SetErrors(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestViewModel.AnotherProperty)); - Assert.Equal(Enumerable.Repeat("Value has to be at least 5.", expectedErrorCount), ((INotifyDataErrorInfo)viewModel).GetErrors(propertyName)); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(nameof(TestViewModel.Property))] - [InlineData("NotExistingProperty")] - public void INotifyDataErrorInfoGetErrors_without_error(string propertyName) - { - var viewModel = new TestViewModel(); - viewModel.SetErrors(Enumerable.Repeat("Value has to be at least 5.", 1), nameof(TestViewModel.Property)); - viewModel.SetErrors(Enumerable.Empty(), nameof(viewModel.Property)); - Assert.Empty(((INotifyDataErrorInfo)viewModel).GetErrors(propertyName)); - } - - #endregion - - #region NotifyErrorsChanged - - [Fact] - public void NotifyErrorsChanged_raises_event_on_ErrorsChanged() - { - var invokedErrorChangedEvents = new List(); - var viewModel = new TestViewModel(); - viewModel.ErrorsChanged += (sender, args) => invokedErrorChangedEvents.Add(args.PropertyName); - - viewModel.NotifyErrorsChangedExternal(nameof(viewModel.Property)); - - Assert.Equal(Enumerable.Repeat(nameof(viewModel.Property), 1), invokedErrorChangedEvents); - } - - #endregion - } -} \ No newline at end of file diff --git a/test/Smaragd.Tests/ViewModels/ViewModelTests.cs b/test/Smaragd.Tests/ViewModels/ViewModelTests.cs index 86ff524..8e7cb90 100644 --- a/test/Smaragd.Tests/ViewModels/ViewModelTests.cs +++ b/test/Smaragd.Tests/ViewModels/ViewModelTests.cs @@ -6,6 +6,8 @@ using NKristek.Smaragd.ViewModels; using NKristek.Smaragd.Helpers; using Xunit; +using System.Linq; +using System.Collections; namespace NKristek.Smaragd.Tests.ViewModels { @@ -99,8 +101,80 @@ public ObservableCollection IsDirtyIgnoredWeakValues get => _isDirtyIgnoredWeakValues?.TargetOrDefault(); set => SetProperty(ref _isDirtyIgnoredWeakValues, value); } + + public void NotifyPropertyChangingExternal(string propertyName) + { + NotifyPropertyChanging(propertyName); + } + + public void NotifyPropertyChangedExternal(string propertyName) + { + NotifyPropertyChanged(propertyName); + } + + public void SetErrorsExternal(IEnumerable errors, string propertyName) + { + SetErrors(errors, propertyName); + } + } + + #region IsValid + + [Fact] + public void IsValid_is_false_when_validation_errors_exist() + { + var viewModel = new TestViewModel(); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + Assert.False(viewModel.IsValid); + } + + [Fact] + public void IsValid_is_true_when_no_validation_errors_exist() + { + var viewModel = new TestViewModel(); + viewModel.SetErrorsExternal(Enumerable.Repeat("error", 1), nameof(viewModel.Property)); + viewModel.SetErrorsExternal(Enumerable.Empty(), nameof(viewModel.Property)); + Assert.True(viewModel.IsValid); } + [Fact] + public void IsValid_does_not_set_IsDirty() + { + var viewModel = new TestViewModel(); + viewModel.NotifyPropertyChangedExternal(nameof(viewModel.IsValid)); + Assert.False(viewModel.IsDirty); + } + + [Fact] + public void IsValid_gets_notified_by_HasErrors() + { + var invokedPropertyChangingEvents = new List(); + var invokedPropertyChangedEvents = new List(); + var viewModel = new TestViewModel(); + viewModel.PropertyChanging += (sender, args) => invokedPropertyChangingEvents.Add(args.PropertyName); + viewModel.PropertyChanged += (sender, args) => invokedPropertyChangedEvents.Add(args.PropertyName); + + viewModel.NotifyPropertyChangingExternal(nameof(viewModel.HasErrors)); + viewModel.NotifyPropertyChangedExternal(nameof(viewModel.HasErrors)); + + Assert.Contains(nameof(viewModel.IsValid), invokedPropertyChangingEvents); + Assert.Contains(nameof(viewModel.IsValid), invokedPropertyChangedEvents); + } + + #endregion + + #region HasErrors + + [Fact] + public void HasErrors_does_not_set_IsDirty() + { + var viewModel = new TestViewModel(); + viewModel.NotifyPropertyChangedExternal(nameof(viewModel.HasErrors)); + Assert.False(viewModel.IsDirty); + } + + #endregion + #region IsDirty [Theory]