public void AsyncValidation_DependantProperties_IfOneInvalidSecondIsInvalidToo() { TestUtils.ExecuteWithDispatcher((dispatcher, testCompleted) => { var vm = new DummyViewModel { Foo = "abc", Bar = "abc" }; Func<bool> validCondition = () => vm.Foo != vm.Bar; var validation = new ValidationHelper(); validation.AddAsyncRule( () => vm.Foo, () => vm.Bar, setResult => { ThreadPool.QueueUserWorkItem(_ => { setResult(RuleResult.Assert(validCondition(), "Foo must be different than bar")); }); }); validation.ValidateAsync(() => vm.Bar).ContinueWith(r => { Assert.False(r.Result.IsValid, "Validation must fail"); Assert.True(r.Result.ErrorList.Count == 2, "There must be 2 errors: one for each dependant property"); testCompleted(); }); }); }
public void AddRequiredRule_RuleIsAddedForSpecifiedPropertyName() { // ARRANGE var vm = new DummyViewModel(); var v = new ValidationHelper(); v.AddRequiredRule(() => vm.Foo, "Foo required."); // ACT var vr = v.Validate(nameof(vm.Foo)); // VERIFY Assert.False(vr.IsValid); Assert.Equal(vr.ErrorList[0].ErrorText, "Foo required."); }
public void AsyncValidation_GeneralSmokeTest() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel {Foo = null}; var validation = new ValidationHelper(); bool ruleExecuted = false; // OK, this is really strange, but if Action<bool> is not mentioned anywhere in the project, then ReSharter would fail to build and run the test... // So including the following line to fix it. Action<RuleResult> dummy = null; Assert.Null(dummy); // Getting rid of the "unused variable" warning. validation.AddAsyncRule(setResult => ThreadPool.QueueUserWorkItem(_ => { ruleExecuted = true; setResult(RuleResult.Invalid("Foo cannot be empty string.")); })); validation.ResultChanged += (o, e) => { Assert.True(ruleExecuted, "Validation rule must be executed before ValidationCompleted event is fired."); var isUiThread = dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; Assert.True(isUiThread, "ValidationResultChanged must be executed on UI thread"); }; var ui = TaskScheduler.FromCurrentSynchronizationContext(); validation.ValidateAllAsync().ContinueWith(r => { var isUiThread = dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; Assert.True(isUiThread, "Validation callback must be executed on UI thread"); Assert.False(r.Result.IsValid, "Validation must fail according to the validaton rule"); Assert.False(validation.GetResult().IsValid, "Validation must fail according to the validaton rule"); Assert.True(ruleExecuted, "Rule must be executed before validation completed callback is executed."); completedAction(); }, ui); }); }
public void AddRequiredRule_AddsRuleThatChecksTheObjectNotNullOrEmptyString() { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); validation.AddRequiredRule(() => dummy.Foo, "Foo cannot be empty"); // ACT var result = validation.ValidateAll(); // VERIFY Assert.False(result.IsValid, "Validation must fail"); // ACT dummy.Foo = "abc"; var resultAfterCorrection = validation.ValidateAll(); // VERIFY Assert.True(resultAfterCorrection.IsValid, "The result must be valid after the correction of the error"); }
public void ResultChanged_CorrectingValidationError_EventIsFiredForWithValidResultAfterCorrection() { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); var fooResult = RuleResult.Valid(); // ReSharper disable AccessToModifiedClosure // Intended validation.AddRule(() => dummy.Foo, () => fooResult); // ReSharper restore AccessToModifiedClosure var onResultChanged = new Action<ValidationResultChangedEventArgs>(r => { }); // ReSharper disable AccessToModifiedClosure // Intended validation.ResultChanged += (o, e) => onResultChanged(e); // ReSharper restore AccessToModifiedClosure // ACT & VERIFY // First, verify that the event is fired with invalid result fooResult = RuleResult.Invalid("Error"); onResultChanged = r => { Assert.False(r.NewResult.IsValid, "ResultChanged must be fired with invalid result first."); }; validation.ValidateAll(); // Second, verify that after second validation when error was corrected, the event fires with the valid result fooResult = RuleResult.Valid(); onResultChanged = r => { Assert.True(r.NewResult.IsValid, "ResultChanged must be fired with valid result after succesfull validation."); }; validation.ValidateAll(); }
public void Reset_ResultChangedFiresForInvalidTargets() { // ARRANGE var dummy = new DummyViewModel(); var validation = new ValidationHelper(); validation.AddRule(RuleResult.Valid); validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("error1")); validation.AddRule(() => dummy.Bar, () => RuleResult.Invalid("error2")); validation.ValidateAll(); bool eventFiredForFoo = false; bool evernFiredForBar = false; validation.ResultChanged += (sender, args) => { if (Equals(args.Target, PropertyName.For(() => dummy.Foo))) { eventFiredForFoo = true; } else if (Equals(args.Target, PropertyName.For(() => dummy.Bar))) { evernFiredForBar = true; } else { Assert.False(true, "ResultChanged event fired for an unexpected target."); } Assert.True(args.NewResult.IsValid); }; // ACT validation.Reset(); // VERIFY Assert.True(eventFiredForFoo); Assert.True(evernFiredForBar); }
public void Reset_AllTargetsBecomeValidAgain() { // ARRANGE var dummy = new DummyViewModel(); var validation = new ValidationHelper(); validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("error1")); validation.AddRule(() => dummy.Bar, () => RuleResult.Invalid("error2")); validation.ValidateAll(); // ACT validation.Reset(); // VERIFY Assert.True(validation.GetResult().IsValid); Assert.True(validation.GetResult(() => dummy.Foo).IsValid); Assert.True(validation.GetResult(() => dummy.Bar).IsValid); }
public void RemoveRule_ThereAreTwoFailedRules_RemoveOne_ResultChangedShouldBeFiredWithNewResultStillInvalid() { // ARRANGE var dummy = new DummyViewModel(); var validation = new ValidationHelper(); validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("error2")); var invalidRule = validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("error")); var validationResult = validation.ValidateAll(); Assert.False(validationResult.IsValid); bool resultChangedEventFired = false; validation.ResultChanged += (sender, args) => { Assert.Equal(PropertyName.For(() => dummy.Foo), args.Target); Assert.False(args.NewResult.IsValid); resultChangedEventFired = true; }; // ACT validation.RemoveRule(invalidRule); // VERIFY Assert.True(resultChangedEventFired); }
public void RemoveAllRules_HadTwoNegativeRulesRegisteredForDifferentTargets_ResultChangedIsFiredForAllTargets() { // ARRANGE var dummy = new DummyViewModel(); var validation = new ValidationHelper(); validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("error1")); validation.AddRule(() => dummy.Bar, () => RuleResult.Invalid("error2")); validation.ValidateAll(); int resultChangedFiredCount = 0; validation.ResultChanged += (sender, args) => { resultChangedFiredCount++; }; // ACT validation.RemoveAllRules(); // VERIFY Assert.Equal(2, resultChangedFiredCount); }
public void ValidateAsync_MultipleRulesForSameTarget_DoesNotExecuteRulesIfPerviousFailed() { TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); bool firstRuleExecuted = false; bool secondRuleExecuted = false; validation.AddRule(() => dummy.Foo, () => { firstRuleExecuted = true; return RuleResult.Invalid("Error1"); }); validation.AddAsyncRule(() => dummy.Foo, onCompleted => { secondRuleExecuted = true; onCompleted(RuleResult.Invalid("Error2")); }); // ACT validation.ValidateAllAsync().ContinueWith(result => { // VERIFY Assert.True(firstRuleExecuted, "First rule must have been executed"); Assert.False(secondRuleExecuted, "Second rule should not have been executed because first rule failed."); completedAction(); }); }); }
public void Validate_MultipleRulesForSameTarget_ClearsResultsBeforeValidation() { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); RuleResult firstRuleResult = RuleResult.Valid(); RuleResult secondRuleResult = RuleResult.Invalid("Error2"); validation.AddRule(() => dummy.Foo, () => { return firstRuleResult; }); validation.AddRule(() => dummy.Foo, () => { return secondRuleResult; }); // ACT validation.ValidateAll(); firstRuleResult = RuleResult.Invalid("Error1"); validation.ValidateAll(); // VERIFY var result = validation.GetResult(() => dummy.Foo); Assert.False(result.IsValid); Assert.Equal(1, result.ErrorList.Count); Assert.Equal("Error1", result.ErrorList[0].ErrorText); }
public void ValidationCompleted_ValidateRuleWithMultipleTargets_ResultContainsErrorsForAllTargsts() { // Arrange var validation = new ValidationHelper(); var dummy = new DummyViewModel(); validation.AddRule(() => dummy.Foo, () => dummy.Bar, () => { return RuleResult.Invalid("Error"); }); // Act var result = validation.ValidateAll(); // Assert Assert.False(result.IsValid); Assert.True(result.ErrorList.Count == 2, "There must be two errors: one for each property target"); Assert.True(Equals(result.ErrorList[0].Target, "dummy.Foo"), "Target for the first error must be dummy.Foo"); Assert.True(Equals(result.ErrorList[1].Target, "dummy.Bar"), "Target for the second error must be dummy.Bar"); }
public void ErrorChanged_RaisedOnUIThread() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); var syncEvent = new ManualResetEvent(false); vm.Validator.AddAsyncRule(() => vm.Foo, setResult => { var t = new Thread(() => setResult(RuleResult.Invalid("Test"))); t.Start(); }); vm.ErrorsChanged += (o, e) => { var threadId = Thread.CurrentThread.ManagedThreadId; dispatcher.BeginInvoke(new Action(() => { Assert.Equal(dispatcher.Thread.ManagedThreadId, threadId); syncEvent.Set(); })); }; vm.Validate(); ThreadPool.QueueUserWorkItem(_ => { if (!syncEvent.WaitOne(TimeSpan.FromSeconds(5))) { dispatcher.BeginInvoke(new Action(() => Assert.True(false, "ErrorsChanged was not raised within specified timeout (5 sec)"))); } completedAction(); }); }); }
public void SyncValidation_SeveralRulesForOneTarget_ValidWhenAllRulesAreValid() { var vm = new DummyViewModel(); var validation = new ValidationHelper(); validation.AddRule(vm, RuleResult.Valid); validation.AddRule(vm, () => { return RuleResult.Invalid("Rule 2 failed"); }); validation.AddRule(vm, RuleResult.Valid); var r = validation.Validate(vm); Assert.False(r.IsValid); }
public void ValidateAsync_WithCallback_ValidationOccuredAndCallbackIsCalledOnUIThread() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); vm.Foo = null; bool ruleExecuted = false; var validation = new ValidationHelper(); validation.AddAsyncRule(setResult => { ruleExecuted = true; setResult(RuleResult.Invalid("Error1")); }); validation.ValidateAllAsync(result => { Assert.True(ruleExecuted, "Validation rule must be executed before validation callback is called."); Assert.Equal(dispatcher.Thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId); completedAction(); }); }); }
public void ValidatedAsync_ExecutedInsideUIThreadTask_ValidationRunsInBackgroundThread() { // This test checks the situation described here: // http://stackoverflow.com/questions/6800705/why-is-taskscheduler-current-the-default-taskscheduler // In short: if a continuation of a task runs on the UI thread, then the tasks that you start using // Task.Factory.StartNew will run also on the UI thread. This is unexpected an may cause a deadlock, or freezing UI. TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); int uiThreadId = Thread.CurrentThread.ManagedThreadId; int validationThreadId = uiThreadId; validation.AddAsyncRule(() => dummy.Foo, onCompleted => { validationThreadId = Thread.CurrentThread.ManagedThreadId; onCompleted(RuleResult.Valid()); }); // ACT // Execute validation within a continuation of another task that runs on // current synchronization context, so that the TaskScheduler.Current is set to the // UI context. Task.Factory.StartNew(() => { }).ContinueWith(t => { Task<ValidationResult> task = validation.ValidateAllAsync(); task.ContinueWith(result => { // VERIFY // Schedule the varification code with the dispatcher in order to take it // out of the ContinueWith continuation. Otherwise the exceptions will be stored inside the task and // unit testing framework will not know about them. uiThreadDispatcher.BeginInvoke(new Action(() => { Assert.False(uiThreadId == validationThreadId, "Validation must be executed in a background thread, so that the UI thread is not blocked."); completedAction(); })); }); }, TaskScheduler.FromCurrentSynchronizationContext()); }); }
public void ValidatedAsync_AsyncRuleDoesnotCallCallback_ThrowsAnExceptionAfterTimeout() { TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); validation.AsyncRuleExecutionTimeout = TimeSpan.FromSeconds(0.1); var dummy = new DummyViewModel(); validation.AddAsyncRule(() => dummy.Foo, onCompleted => { // Do nothing }); // ACT var ui = TaskScheduler.FromCurrentSynchronizationContext(); validation.ValidateAllAsync().ContinueWith(result => { Assert.True(result.IsFaulted, "Validation task must fail."); Assert.NotNull(result.Exception); completedAction(); }, ui); }); }
public void ResultChanged_RuleErrorsChangedButRuleValidityDidNotChange_EventStillFires() { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); validation.AddRule(() => dummy.Foo, () => { if (string.IsNullOrEmpty(dummy.Foo)) { return RuleResult.Invalid("Foo should not be empty"); } return RuleResult.Assert(dummy.Foo.Length > 5, "Length must be greater than 5").Combine( RuleResult.Assert(dummy.Foo.Any(Char.IsDigit), "Must contain digit")); }); var resultChangedCalledTimes = 0; const int expectedResultChangedCalls = 1 /* First invalid value */+ 1 /* Second invalid value */+ 1 /* Third invalid value */ + 1 /* Valid value */; validation.ResultChanged += (o, e) => { resultChangedCalledTimes++; }; // ACT dummy.Foo = null; // Should generage "Foo should not be empty" error validation.ValidateAll(); dummy.Foo = "123"; // Should generate the "Length must be greater than 5" error validation.ValidateAll(); dummy.Foo = "sdfldlssd"; // Should generate the "Must contain digit" error validation.ValidateAll(); dummy.Foo = "lsdklfjsld2342"; // Now should be valid validation.ValidateAll(); // VERIFY Assert.Equal(expectedResultChangedCalls, resultChangedCalledTimes); }
public void AsyncValidation_MixedAsyncAndNotAsyncRules_AllExecutedBeforeValidationCompleted() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); var validation = new ValidationHelper(); bool rule1Executed = false; validation.AddAsyncRule(vm, setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule1Executed = true; setResultDelegate(RuleResult.Valid()); })); bool rule2Executed = false; validation.AddRule(vm, () => { rule2Executed = true; return RuleResult.Valid(); }); bool rule3Executed = false; validation.AddAsyncRule(vm, setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule3Executed = true; setResultDelegate(RuleResult.Valid()); })); validation.ValidateAllAsync().ContinueWith(r => { Assert.True(rule1Executed); Assert.True(rule2Executed); Assert.True(rule3Executed); Assert.True(r.Result.IsValid); completedAction(); }); }); }
public void ResultChanged_ValidateExecutedForSeveralRules_FiresForEachTarget() { // Arrange var validation = new ValidationHelper(); var dummy = new DummyViewModel(); validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("Error")); validation.AddRule(() => dummy.Foo, RuleResult.Valid); validation.AddRule(() => dummy.Bar, RuleResult.Valid); validation.AddRule(() => RuleResult.Invalid("Error")); const int expectedTimesToFire = 0 + 1 /*Invalid Foo*/+ 1 /* Invalid general target */; var eventFiredTimes = 0; validation.ResultChanged += (o, e) => { eventFiredTimes++; }; // Act validation.ValidateAll(); // Verify Assert.Equal(expectedTimesToFire, eventFiredTimes); }
public void AsyncValidation_SyncRule_ExecutedAsyncroniously() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); vm.Foo = null; var validation = new ValidationHelper(); validation.AddRule(() => { Assert.False(dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId, "Rule must be executed in a background thread."); if (string.IsNullOrEmpty(vm.Foo)) { return RuleResult.Invalid("Foo cannot be empty string."); } return RuleResult.Valid(); }); validation.ValidateAllAsync().ContinueWith(r => completedAction()); }); }
public void Validate_MultipleRulesForSameTarget_DoesNotExecuteRulesIfPerviousFailed() { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); bool firstRuleExecuted = false; bool secondRuleExecuted = false; validation.AddRule(() => dummy.Foo, () => { firstRuleExecuted = true; return RuleResult.Invalid("Error1"); }); validation.AddRule(() => dummy.Foo, () => { secondRuleExecuted = true; return RuleResult.Invalid("Error2"); }); // ACT validation.ValidateAll(); // VERIFY Assert.True(firstRuleExecuted, "First rule must have been executed"); Assert.False(secondRuleExecuted, "Second rule should not have been executed because first rule failed."); }
public void Validate_MultipleRulesForSameTarget_OptionToAllowValidationOnFailedTargetsIsSetForSpecificRule_ThatRuleIsExecuted() { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); bool firstRuleExecuted = false; bool secondRuleExecuted = false; bool thirdRuleExecuted = false; validation.AddRule(nameof(dummy.Foo), () => { firstRuleExecuted = true; return RuleResult.Invalid("Error1"); }); validation.AddRule(nameof(dummy.Foo), () => { secondRuleExecuted = true; return RuleResult.Invalid("Error2"); }); validation.AddRule(nameof(dummy.Foo), () => { thirdRuleExecuted = true; return RuleResult.Invalid("Error3"); }).WithSettings(s => s.ExecuteOnAlreadyInvalidTarget = true); // ACT validation.ValidateAll(); // VERIFY Assert.True(firstRuleExecuted, "First rule must have been executed"); Assert.False(secondRuleExecuted, "Second rule should not have been executed because first rule failed."); Assert.True(thirdRuleExecuted, "Third rule should be executed because it is configured to be executed on already invalid target."); }
public void ValidationResultChanged_ValidateExecutedForOneRule_FiresOneTime() { // Arrange var validation = new ValidationHelper(); var dummy = new DummyViewModel(); validation.AddRule(() => dummy.Foo, () => RuleResult.Invalid("Error")); var eventFiredTimes = 0; validation.ResultChanged += (o, e) => { eventFiredTimes++; }; // Act validation.ValidateAll(); // Verity Assert.Equal(1, eventFiredTimes); }
public void Validate_MultipleRulesForSameTarget_OptionToAllowValidationOnFailedTargetsIsSetGlobally_AllRulesAreExecuted() { // ARRANGE var validation = new ValidationHelper(new ValidationSettings { DefaultRuleSettings = new ValidationRuleSettings { ExecuteOnAlreadyInvalidTarget = true } }); var dummy = new DummyViewModel(); bool firstRuleExecuted = false; bool secondRuleExecuted = false; validation.AddRule(nameof(dummy.Foo), () => { firstRuleExecuted = true; return RuleResult.Invalid("Error1"); }); validation.AddRule(nameof(dummy.Foo), () => { secondRuleExecuted = true; return RuleResult.Invalid("Error2"); }); // ACT validation.ValidateAll(); // VERIFY Assert.True(firstRuleExecuted, "First rule must have been executed"); Assert.True(secondRuleExecuted, "Second rule should be executed as well even though the target is already invalid."); }