Skip to content

Commit aa218ee

Browse files
authored
Feature extension and testcase for new ModelObservableValidation (#59)
* extension method for overload with property lambda * testcase for new functionality
1 parent 710ee6f commit aa218ee

File tree

5 files changed

+81
-1
lines changed

5 files changed

+81
-1
lines changed

src/ReactiveUI.Validation.Tests/API/ApiApprovalTests.ValidationProject.net472.approved.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ namespace ReactiveUI.Validation.Extensions
134134
where TViewModel : ReactiveUI.ReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
135135
public static ReactiveUI.Validation.Helpers.ValidationHelper ValidationRule<TViewModel>(this TViewModel viewModel, System.Func<TViewModel, System.IObservable<bool>> viewModelObservableProperty, System.Func<TViewModel, bool, string> messageFunc)
136136
where TViewModel : ReactiveUI.ReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
137+
public static ReactiveUI.Validation.Helpers.ValidationHelper ValidationRule<TViewModel, TViewModelProp>(this TViewModel viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel, TViewModelProp>> viewModelProperty, System.Func<TViewModel, System.IObservable<bool>> viewModelObservableProperty, System.Func<TViewModel, bool, string> messageFunc)
138+
where TViewModel : ReactiveUI.ReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
137139
}
138140
public class static ValidationContextExtensions
139141
{

src/ReactiveUI.Validation.Tests/API/ApiApprovalTests.ValidationProject.netcoreapp2.1.approved.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ namespace ReactiveUI.Validation.Extensions
134134
where TViewModel : ReactiveUI.ReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
135135
public static ReactiveUI.Validation.Helpers.ValidationHelper ValidationRule<TViewModel>(this TViewModel viewModel, System.Func<TViewModel, System.IObservable<bool>> viewModelObservableProperty, System.Func<TViewModel, bool, string> messageFunc)
136136
where TViewModel : ReactiveUI.ReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
137+
public static ReactiveUI.Validation.Helpers.ValidationHelper ValidationRule<TViewModel, TViewModelProp>(this TViewModel viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel, TViewModelProp>> viewModelProperty, System.Func<TViewModel, System.IObservable<bool>> viewModelObservableProperty, System.Func<TViewModel, bool, string> messageFunc)
138+
where TViewModel : ReactiveUI.ReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
137139
}
138140
public class static ValidationContextExtensions
139141
{

src/ReactiveUI.Validation.Tests/Models/IndeiTestViewModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace ReactiveUI.Validation.Tests.Models
99
public class IndeiTestViewModel : ReactiveValidationObject<IndeiTestViewModel>
1010
{
1111
private string _name;
12+
private string _otherName;
1213

1314
/// <summary>
1415
/// Initializes a new instance of the <see cref="IndeiTestViewModel"/> class.
@@ -26,5 +27,14 @@ public string Name
2627
get => _name;
2728
set => this.RaiseAndSetIfChanged(ref _name, value);
2829
}
30+
31+
/// <summary>
32+
/// Gets or sets get the Name.
33+
/// </summary>
34+
public string OtherName
35+
{
36+
get => _otherName;
37+
set => this.RaiseAndSetIfChanged(ref _otherName, value);
38+
}
2939
}
3040
}

src/ReactiveUI.Validation.Tests/NotifyDataErrorInfoTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.ComponentModel;
22
using System.Linq;
3+
using System.Reactive.Linq;
34
using ReactiveUI.Validation.Components;
45
using ReactiveUI.Validation.Extensions;
56
using ReactiveUI.Validation.Tests.Models;
@@ -121,5 +122,42 @@ public void ShouldFireErrorsChangedEventWhenValidationStateChanges()
121122
Assert.NotNull(arguments);
122123
Assert.Equal(string.Empty, arguments.PropertyName);
123124
}
125+
126+
/// <summary>
127+
/// Using ModelObservableValidation with NotifyDataErrorInfo should return errors when associated property changes.
128+
/// </summary>
129+
[Fact]
130+
public void ShouldDeliverErrorsWhenModelObservableValidationTriggers()
131+
{
132+
var viewModel = new IndeiTestViewModel();
133+
134+
string namesShouldMatchMessage = "names should match.";
135+
var validation = new ModelObservableValidation<IndeiTestViewModel, string>(
136+
viewModel,
137+
vm => vm.OtherName,
138+
vm => vm.WhenAnyValue(
139+
m => m.Name,
140+
m => m.OtherName,
141+
(n, on) => new { n, on })
142+
.Select(bothNames => bothNames.n == bothNames.on),
143+
(_, isValid) => isValid ? string.Empty : namesShouldMatchMessage);
144+
145+
viewModel.ValidationContext.Add(validation);
146+
147+
Assert.False(viewModel.HasErrors);
148+
Assert.True(viewModel.ValidationContext.IsValid);
149+
Assert.Single(viewModel.ValidationContext.Validations);
150+
Assert.Empty(viewModel.GetErrors(nameof(viewModel.Name)).Cast<string>());
151+
Assert.Empty(viewModel.GetErrors(nameof(viewModel.OtherName)).Cast<string>());
152+
153+
viewModel.Name = "JoJo";
154+
viewModel.OtherName = "NoNo";
155+
156+
Assert.True(viewModel.HasErrors);
157+
Assert.Empty(viewModel.GetErrors(nameof(viewModel.Name)).Cast<string>());
158+
Assert.Single(viewModel.GetErrors(nameof(viewModel.OtherName)).Cast<string>());
159+
Assert.Single(validation.Text);
160+
Assert.Equal(namesShouldMatchMessage, validation.Text.Single());
161+
}
124162
}
125163
}

src/ReactiveUI.Validation/Extensions/SupportsValidationExtensions.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,34 @@ public static ValidationHelper ValidationRule<TViewModel>(
103103
return new ValidationHelper(validation);
104104
}
105105

106+
/// <summary>
107+
/// Setup a validation rule with a general observable indicating validity.
108+
/// </summary>
109+
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
110+
/// <typeparam name="TViewModelProp">ViewModel property type.</typeparam>
111+
/// <param name="viewModel">ViewModel instance.</param>
112+
/// <param name="viewModelProperty">ViewModel property referenced in viewModelObservableProperty.</param>
113+
/// <param name="viewModelObservableProperty">Func to define if the viewModel is valid or not.</param>
114+
/// <param name="messageFunc">Func to define the validation error message based on the viewModel and viewModelObservableProperty values.</param>
115+
/// <returns>Returns a <see cref="ValidationHelper"/> object.</returns>
116+
/// <remarks>
117+
/// It should be noted that the observable should provide an initial value, otherwise that can result
118+
/// in an inconsistent performance.
119+
/// </remarks>
120+
public static ValidationHelper ValidationRule<TViewModel, TViewModelProp>(
121+
this TViewModel viewModel,
122+
Expression<Func<TViewModel, TViewModelProp>> viewModelProperty,
123+
Func<TViewModel, IObservable<bool>> viewModelObservableProperty,
124+
Func<TViewModel, bool, string> messageFunc)
125+
where TViewModel : ReactiveObject, IValidatableViewModel
126+
{
127+
var validation = new ModelObservableValidation<TViewModel, TViewModelProp>(viewModel, viewModelProperty, viewModelObservableProperty, messageFunc);
128+
129+
viewModel.ValidationContext.Add(validation);
130+
131+
return new ValidationHelper(validation);
132+
}
133+
106134
/// <summary>
107135
/// Gets an observable for the validity of the ViewModel.
108136
/// </summary>
@@ -115,4 +143,4 @@ public static IObservable<bool> IsValid<TViewModel>(this TViewModel viewModel)
115143
return viewModel?.ValidationContext.Valid;
116144
}
117145
}
118-
}
146+
}

0 commit comments

Comments
 (0)