public void CancelEdit_Restores_ValidationErrors_ForAddedEntity()
        {
            // Start with a valid entity
            Cities.City city = new Cities.City() { Name = "Cincinnati", StateName = "OH", CountyName = "Hamilton" };

            IEditableObject editableCity = (IEditableObject)city;

            string customEntityError = "Entity-level error that was added manually";
            city.ValidationErrors.Add(new ValidationResult(customEntityError));

            string customPropertyError = "Property-level error that was added manually";
            city.ValidationErrors.Add(new ValidationResult(customPropertyError, new string[] { "ZoneID", "CountyName" }));

            Assert.AreEqual<int>(2, city.ValidationErrors.Count, "Error count before editing");

            // Edit the city, making changes that will replace the property errors for Name and CountyName (because it was tied to the Name member too)
            // with new errors, leaving the entity-level error in place.
            editableCity.BeginEdit();
            city.ZoneID = -1; // Out of range
            city.ZoneName = Cities.CityPropertyValidator.InvalidZoneName;
            Assert.AreEqual<int>(3, city.ValidationErrors.Count, "Error count before CancelEdit");

            INotifyDataErrorInfo notifier = (INotifyDataErrorInfo)city;
            List<Tuple<string, IEnumerable<ValidationResult>>> errorNotifications = new List<Tuple<string, IEnumerable<ValidationResult>>>();
            notifier.ErrorsChanged += (s, e) =>
                {
                    errorNotifications.Add(new Tuple<string, IEnumerable<ValidationResult>>(e.PropertyName, notifier.GetErrors(e.PropertyName).Cast<ValidationResult>()));
                };

            /// When we cancel the edit, the following changes will occur to the validation errors, in no particular order
            /// - StateName is no longer invalid; notification, with no errors for that property
            /// - Name loses its required error but regains the manually-added custom error
            /// - CountyName regains the manually-added error
            /// - The custom entity-level error message that was added will be restored

            editableCity.CancelEdit();
            // Verify our validation errors count reverted back to the 2 errors we had before editing
            Assert.AreEqual<int>(2, city.ValidationErrors.Count, "Error count after CancelEdit");

            // Verify the entity-level error that we manually added still shows up
            Assert.AreEqual<string>(customEntityError, city.ValidationErrors.Single(e => e.MemberNames.Count() == 0 || (e.MemberNames.Count() == 1 && string.IsNullOrEmpty(e.MemberNames.Single()))).ErrorMessage, "ErrorMessage after CancelEdit");

            // Verify the property-level error that we manually added still shows up
            Assert.AreEqual<string>(customPropertyError, city.ValidationErrors.Single(e => e.MemberNames.Contains("ZoneID")).ErrorMessage, "ErrorMessage for ZoneID after CancelEdit");
            Assert.AreEqual<string>(customPropertyError, city.ValidationErrors.Single(e => e.MemberNames.Contains("CountyName")).ErrorMessage, "ErrorMessage for CountyName after CancelEdit");

            // Verify that we got the 4 expected notifications from INotifyDataErrorInfo
            Assert.AreEqual<int>(4, errorNotifications.Count, "Error notification count");

            // One of which should have been for Name, one for StateName, one for CountyName, and one for the entity-level error
            Assert.AreEqual<int>(1, errorNotifications.Count(e => e.Item1 == "ZoneID"), "Count of Name notifications");
            Assert.AreEqual<int>(1, errorNotifications.Count(e => e.Item1 == "ZoneName"), "Count of ZoneName notifications");
            Assert.AreEqual<int>(1, errorNotifications.Count(e => e.Item1 == "CountyName"), "Count of CountyName notifications");
            Assert.AreEqual<int>(1, errorNotifications.Count(e => string.IsNullOrEmpty(e.Item1)), "Count of entity-level notifications");

            // Verify that when the notification occurred for Name, CountyName, and the entity, we had a single error for each, and there was no error for StateName
            Assert.AreEqual<int>(1, errorNotifications.Single(e => e.Item1 == "ZoneID").Item2.Count(), "Error count for ZoneID at time of notification");
            Assert.AreEqual<int>(1, errorNotifications.Single(e => e.Item1 == "CountyName").Item2.Count(), "Error count for CountyName at time of notification");
            Assert.AreEqual<int>(1, errorNotifications.Single(e => string.IsNullOrEmpty(e.Item1)).Item2.Count(), "Error count for entity errors at time of notification");
            Assert.AreEqual<int>(0, errorNotifications.Single(e => e.Item1 == "ZoneName").Item2.Count(), "Error count for ZoneName at time of notification");

            // Verify the manually-added errors were in place at the time of the notifications
            Assert.AreEqual<string>(customPropertyError, errorNotifications.Single(e => e.Item1 == "ZoneID").Item2.Single().ErrorMessage, "ErrorMessage of the ZoneID error when its notification was raised");
            Assert.AreEqual<string>(customPropertyError, errorNotifications.Single(e => e.Item1 == "CountyName").Item2.Single().ErrorMessage, "ErrorMessage of the CountyName error when its notification was raised");
            Assert.AreEqual<string>(customEntityError, errorNotifications.Single(e => string.IsNullOrEmpty(e.Item1)).Item2.Single().ErrorMessage, "ErrorMessage of the entity-level error when its notification was raised");
        }
        public void ErrorsChanged_Event_Subscription()
        {
            // Start with a valid entity
            Cities.City city = new Cities.City() { Name = "Cincinnati", StateName = "OH", CountyName = "Hamilton" };

            INotifyDataErrorInfo notifier = city as INotifyDataErrorInfo;

            int errorsChangedCount = 0;
            EventHandler<DataErrorsChangedEventArgs> handler = (s, e) => ++errorsChangedCount;
            notifier.ErrorsChanged += handler;

            city.ValidationErrors.Add(new ValidationResult("Foo"));
            Assert.AreEqual<int>(1, errorsChangedCount, "Error count after subscribing");

            notifier.ErrorsChanged -= handler;
            city.ValidationErrors.Add(new ValidationResult("Bar"));
            Assert.AreEqual<int>(1, errorsChangedCount, "Error count after unsubscribing");
        }
        public void GetErrors_Prevents_Deferred_Enumeration()
        {
            Cities.City city = new Cities.City();
            INotifyDataErrorInfo notifier = (INotifyDataErrorInfo)city;
            System.Collections.IEnumerable noErrors = notifier.GetErrors("Name");

            city.ValidationErrors.Add(new ValidationResult("Error", new string[] { "Name" }));
            System.Collections.IEnumerable withError = notifier.GetErrors("Name");

            Assert.AreEqual<int>(0, noErrors.Cast<ValidationResult>().Count(), "Count from first enumerable");
            Assert.AreEqual<int>(1, withError.Cast<ValidationResult>().Count(), "Count from second enumerable");
        }
        public void ValidatePropertyOverrideCanThrowValidationException()
        {
            Cities.City city = new Cities.City() { Name = "Cincinnati" };
            city.ThrowValidationExceptions = true;

            string invalidName = "This 1 is an invalid city name";
            ExceptionHelper.ExpectValidationException(() => city.Name = invalidName, "The field CityName must match the regular expression '^[A-Z]+[a-z A-Z]*$'.", typeof(RegularExpressionAttribute), invalidName);
            Assert.AreEqual<string>("Cincinnati", city.Name, "The city name should be unchanged when invalid");
        }
        public void Entity_RejectChanges_Clears_ValidationErrors()
        {
            // This test requires an entity that is attached
            Cities.CityDomainContext domainContext = new Cities.CityDomainContext();
            Cities.City entity = new Cities.City();
            domainContext.Cities.Add(entity);

            INotifyDataErrorInfo notifier = (INotifyDataErrorInfo)entity;
            List<string> actualErrors = new List<string>();
            notifier.ErrorsChanged += (s, e) => actualErrors.Add(e.PropertyName);

            entity.StateName = "Not a State Name"; // Marks the entity as changed and adds a validation result for StateName
            entity.ValidationErrors.Add(new ValidationResult("Invalid Property Error", new string[] { "Foo" }));
            entity.ValidationErrors.Add(new ValidationResult("Entity Error", null));

            string[] membersNotifed = new string[] { "StateName", "Foo", null };
            Assert.IsTrue(actualErrors.OrderBy(s => s).SequenceEqual(membersNotifed.OrderBy(s => s)), "The list of errors when adding errors");
            actualErrors.Clear();

            ((IRevertibleChangeTracking)entity).RejectChanges();
            Assert.IsTrue(actualErrors.OrderBy(s => s).SequenceEqual(membersNotifed.OrderBy(s => s)), "The list of errors when rejecting changes");
        }
        public void ValidationContextUpdatedForEntityWhenChangedForDomainContext()
        {
            Cities.CityDomainContext domainContext = new Cities.CityDomainContext(TestURIs.Cities);

            Cities.City newCity = new Cities.City();
            domainContext.Cities.Add(newCity);

            // Set up the ValidationContext after adding the entity into the domain context
            // to ensure that the updated validation context is plumbed through
            Dictionary<object, object> items = new Dictionary<object, object>();
            items.Add("TestMethod", "ValidationContextUsedForPropertyValidation");
            ValidationContext providedValidationContext = new ValidationContext(this, null, items);
            domainContext.ValidationContext = providedValidationContext;

            bool callbackCalled = false;

            Action<ValidationContext> assertValidationContext = validationValidationContext =>
            {
                Assert.AreNotSame(providedValidationContext, validationValidationContext, "The ValidationContext provided to ValidationProperty should not be the same actual instance of the ValidationContext we provided");
                Assert.IsTrue(validationValidationContext.Items.ContainsKey("TestMethod"), "The ValidationContext provided should have the items we provided");
                Assert.AreEqual(providedValidationContext.Items["TestMethod"], validationValidationContext.Items["TestMethod"], "The ValidationContext provided should have the items we provided");

                callbackCalled = true;
            };

            newCity.ValidatePropertyCallback = assertValidationContext;
            newCity.ValidateCityCallback = assertValidationContext;

            // Entity-level validation is performed by calling EndEdit with valid properties
            IEditableObject editableCity = (IEditableObject)newCity;
            editableCity.BeginEdit();
            newCity.Name = "Cincinnati";
            newCity.StateName = "OH";
            newCity.CountyName = "Hamilton";
            editableCity.EndEdit();

            Assert.IsTrue(callbackCalled, "Make sure our callback was called to perform the test");
        }
        public void ValidationContextUsedForPropertyValidation()
        {
            Dictionary<object, object> items = new Dictionary<object,object>();
            items.Add("TestMethod", "ValidationContextUsedForPropertyValidation");
            ValidationContext providedValidationContext = new ValidationContext(this, null, items);

            Cities.CityDomainContext domainContext = new Cities.CityDomainContext(TestURIs.Cities);
            domainContext.ValidationContext = providedValidationContext;

            bool callbackCalled = false;

            Cities.City newCity = new Cities.City();
            domainContext.Cities.Add(newCity);

            newCity.ValidatePropertyCallback = validationValidationContext =>
                {
                    Assert.AreNotSame(providedValidationContext, validationValidationContext, "The ValidationContext provided to ValidationProperty should not be the same actual instance of the ValidationContext we provided");
                    Assert.IsTrue(validationValidationContext.Items.ContainsKey("TestMethod"), "The ValidationContext provided should have the items we provided");
                    Assert.AreEqual(providedValidationContext.Items["TestMethod"], validationValidationContext.Items["TestMethod"], "The ValidationContext provided should have the items we provided");

                    callbackCalled = true;
                };

            // Set a property, triggering property validation
            newCity.Name = "Foo";
            Assert.IsTrue(callbackCalled, "Make sure our callback was called to perform the test");
        }