public async Task SuppliesFieldClassCorrespondingToFieldState() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); // Act/Assert: Initally, it's valid and unmodified var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); Assert.Equal("valid", inputComponent.FieldClass); Assert.Equal("valid", inputComponent.CssClass); // Same because no Class was specified // Act/Assert: Modify the field rootComponent.EditContext.NotifyFieldChanged(fieldIdentifier); Assert.Equal("modified valid", inputComponent.FieldClass); Assert.Equal("modified valid", inputComponent.CssClass); // Act/Assert: Make it invalid var messages = new ValidationMessageStore(rootComponent.EditContext); messages.Add(fieldIdentifier, "I do not like this value"); Assert.Equal("modified invalid", inputComponent.FieldClass); Assert.Equal("modified invalid", inputComponent.CssClass); // Act/Assert: Clear the modification flag rootComponent.EditContext.MarkAsUnmodified(fieldIdentifier); Assert.Equal("invalid", inputComponent.FieldClass); Assert.Equal("invalid", inputComponent.CssClass); // Act/Assert: Make it valid messages.Clear(); Assert.Equal("valid", inputComponent.FieldClass); Assert.Equal("valid", inputComponent.CssClass); }
public void RequestsValidationWhenValidateIsCalled() { // Arrange var editContext = new EditContext(new object()); var messages = new ValidationMessageStore(editContext); editContext.OnValidationRequested += (sender, eventArgs) => { Assert.Same(editContext, sender); Assert.NotNull(eventArgs); messages.Add( new FieldIdentifier(new object(), "some field"), "Some message"); }; // Act var isValid = editContext.Validate(); // assert Assert.False(isValid); Assert.Equal(new[] { "Some message" }, editContext.GetValidationMessages()); }
public async Task AriaAttributeRemovedWhenStateChangesToValidFromInvalid() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var renderer = new TestRenderer(); var messageStore = new ValidationMessageStore(rootComponent.EditContext); messageStore.Add(fieldIdentifier, "Artificial error message"); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is invalid var batch1 = renderer.Batches.Single(); var componentFrame1 = batch1.GetComponentFrames <TestInputComponent <string> >().Single(); var inputComponentId = componentFrame1.ComponentId; var component = (TestInputComponent <string>)componentFrame1.Component; Assert.Equal("invalid", component.CssClass); Assert.NotNull(component.AdditionalAttributes); Assert.True(component.AdditionalAttributes.ContainsKey("aria-invalid")); // Act: update the field state in the EditContext and notify messageStore.Clear(fieldIdentifier); await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged); // Assert: The input component rendered itself again and now has the new class var batch2 = renderer.Batches.Skip(1).Single(); Assert.Equal(inputComponentId, batch2.DiffsByComponentId.Keys.Single()); Assert.Equal("valid", component.CssClass); Assert.Null(component.AdditionalAttributes); }
/// <summary> /// Validate the whole form and trigger client UI update. /// </summary> /// <param name="editContext"></param> /// <param name="messages"></param> private async void ValidateModel(EditContext editContext, ValidationMessageStore messages) { // <EditForm> should now be able to run async validations: // https://github.com/dotnet/aspnetcore/issues/11914 var validationResults = await TryValidateModel(editContext); messages.Clear(); var graph = new ModelGraphCache(editContext.Model); foreach (var error in validationResults.Errors) { var(propertyValue, propertyName) = graph.EvalObjectProperty(error.PropertyName); // while it is impossible to have a validation error for a null child property, better be safe than sorry... if (propertyValue != null) { var fieldID = new FieldIdentifier(propertyValue, propertyName); messages.Add(fieldID, error.ErrorMessage); } } editContext.NotifyValidationStateChanged(); }
/// <summary> /// Validate the whole form and trigger client UI update. /// </summary> /// <param name="editContext"></param> /// <param name="messages"></param> private void ValidateModel(EditContext editContext, ValidationMessageStore messages) { // WARNING: DO NOT USE Async Void + ValidateAsync here // Explanation: Blazor UI will get VERY BUGGY for some reason if you do that. (Field CSS lagged behind validation) var validationResults = TryValidateModel(editContext); messages.Clear(); var graph = new ModelGraphCache(editContext.Model); foreach (var error in validationResults.Errors) { var(propertyValue, propertyName) = graph.EvalObjectProperty(error.PropertyName); // while it is impossible to have a validation error for a null child property, better be safe than sorry... if (propertyValue != null) { var fieldID = new FieldIdentifier(propertyValue, propertyName); messages.Add(fieldID, error.ErrorMessage); } } editContext.NotifyValidationStateChanged(); }
/// <summary> /// Adds a validation message for the specified field. /// </summary> /// <param name="store">The <see cref="ValidationMessageStore"/>.</param> /// <param name="accessor">Identifies the field for which to add the message.</param> /// <param name="message">The validation message.</param> public static void Add(this ValidationMessageStore store, Expression <Func <object> > accessor, string message) => store.Add(FieldIdentifier.Create(accessor), message);