Наследование: ValidatableViewModel
		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.");
        }