public void NotifiesValidationStateChangedAfterObjectValidation()
        {
            // Arrange
            var model = new TestModel {
                IntFrom1To100 = 101
            };
            var editContext = new EditContext(model);

            editContext.EnableDataAnnotationsValidation();
            var onValidationStateChangedCount = 0;

            editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++;

            // Act/Assert 1: Notifies after invalid results
            Assert.False(editContext.Validate());
            Assert.Equal(1, onValidationStateChangedCount);

            // Act/Assert 2: Notifies after valid results
            model.RequiredString = "Hello";
            model.IntFrom1To100  = 100;
            Assert.True(editContext.Validate());
            Assert.Equal(2, onValidationStateChangedCount);

            // Act/Assert 3: Notifies even if results haven't changed. Later we might change the
            // logic to track the previous results and compare with the new ones, but that's just
            // an optimization. It's legal to notify regardless.
            Assert.True(editContext.Validate());
            Assert.Equal(3, onValidationStateChangedCount);
        }
        public void GetsValidationMessagesFromDataAnnotations()
        {
            // Arrange
            var model = new TestModel {
                IntFrom1To100 = 101
            };
            var editContext = new EditContext(model);

            editContext.EnableDataAnnotationsValidation();

            // Act
            var isValid = editContext.Validate();

            // Assert
            Assert.False(isValid);

            Assert.Equal(new string[]
            {
                "RequiredString:required",
                "IntFrom1To100:range"
            },
                         editContext.GetValidationMessages());

            Assert.Equal(new string[] { "RequiredString:required" },
                         editContext.GetValidationMessages(editContext.Field(nameof(TestModel.RequiredString))));

            // This shows we're including non-[Required] properties in the validation results, i.e,
            // that we're correctly passing "validateAllProperties: true" to DataAnnotations
            Assert.Equal(new string[] { "IntFrom1To100:range" },
                         editContext.GetValidationMessages(editContext.Field(nameof(TestModel.IntFrom1To100))));
        }
        public void ClearsExistingValidationMessagesOnFurtherRuns()
        {
            // Arrange
            var model = new TestModel {
                IntFrom1To100 = 101
            };
            var editContext = new EditContext(model);

            editContext.EnableDataAnnotationsValidation();

            // Act/Assert 1: Initially invalid
            Assert.False(editContext.Validate());

            // Act/Assert 2: Can become valid
            model.RequiredString = "Hello";
            model.IntFrom1To100  = 100;
            Assert.True(editContext.Validate());
        }
        public void CanDetachFromEditContext()
        {
            // Arrange
            var model = new TestModel {
                IntFrom1To100 = 101
            };
            var editContext  = new EditContext(model);
            var subscription = editContext.EnableDataAnnotationsValidation();

            // Act/Assert 1: when we're attached
            Assert.False(editContext.Validate());
            Assert.NotEmpty(editContext.GetValidationMessages());

            // Act/Assert 2: when we're detached
            subscription.Dispose();
            Assert.True(editContext.Validate());
            Assert.Empty(editContext.GetValidationMessages());
        }
        public void IgnoresFieldChangesThatDoNotCorrespondToAValidatableProperty(string fieldName)
        {
            // Arrange
            var editContext = new EditContext(new TestModel());

            editContext.EnableDataAnnotationsValidation();
            var onValidationStateChangedCount = 0;

            editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++;

            // Act/Assert: Ignores field changes that don't correspond to a validatable property
            editContext.NotifyFieldChanged(editContext.Field(fieldName));
            Assert.Equal(0, onValidationStateChangedCount);

            // Act/Assert: For sanity, observe that we would have validated if it was a validatable property
            editContext.NotifyFieldChanged(editContext.Field(nameof(TestModel.RequiredString)));
            Assert.Equal(1, onValidationStateChangedCount);
        }
        public void PerformsPerPropertyValidationOnFieldChange()
        {
            // Arrange
            var model = new TestModel {
                IntFrom1To100 = 101
            };
            var independentTopLevelModel = new object(); // To show we can validate things on any model, not just the top-level one
            var editContext = new EditContext(independentTopLevelModel);

            editContext.EnableDataAnnotationsValidation();
            var onValidationStateChangedCount = 0;
            var requiredStringIdentifier      = new FieldIdentifier(model, nameof(TestModel.RequiredString));
            var intFrom1To100Identifier       = new FieldIdentifier(model, nameof(TestModel.IntFrom1To100));

            editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++;

            // Act/Assert 1: Notify about RequiredString
            // Only RequiredString gets validated, even though IntFrom1To100 also holds an invalid value
            editContext.NotifyFieldChanged(requiredStringIdentifier);
            Assert.Equal(1, onValidationStateChangedCount);
            Assert.Equal(new[] { "RequiredString:required" }, editContext.GetValidationMessages());

            // Act/Assert 2: Fix RequiredString, but only notify about IntFrom1To100
            // Only IntFrom1To100 gets validated; messages for RequiredString are left unchanged
            model.RequiredString = "This string is very cool and very legal";
            editContext.NotifyFieldChanged(intFrom1To100Identifier);
            Assert.Equal(2, onValidationStateChangedCount);
            Assert.Equal(new string[]
            {
                "RequiredString:required",
                "IntFrom1To100:range"
            },
                         editContext.GetValidationMessages());

            // Act/Assert 3: Notify about RequiredString
            editContext.NotifyFieldChanged(requiredStringIdentifier);
            Assert.Equal(3, onValidationStateChangedCount);
            Assert.Equal(new[] { "IntFrom1To100:range" }, editContext.GetValidationMessages());
        }