public async Task UnsubscribesFromValidationStateChangeNotifications() { // 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 rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); var component = renderer.Batches.Single().GetComponentFrames <TestInputComponent <string> >().Single().Component; // Act: dispose, then update the field state in the EditContext and notify ((IDisposable)component).Dispose(); var messageStore = new ValidationMessageStore(rootComponent.EditContext); messageStore.Add(fieldIdentifier, "Some message"); await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged); // Assert: No additional render Assert.Empty(renderer.Batches.Skip(1)); }
public async Task CssClassCombinesClassWithFieldClass() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { AdditionalAttributes = new Dictionary <string, object>() { { "class", "my-class other-class" }, }, EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); // Act/Assert var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); Assert.Equal("valid", inputComponent.FieldClass); Assert.Equal("my-class other-class valid", inputComponent.CssClass); // Act/Assert: Retains custom class when changing field class rootComponent.EditContext.NotifyFieldChanged(fieldIdentifier); Assert.Equal("modified valid", inputComponent.FieldClass); Assert.Equal("my-class other-class modified valid", inputComponent.CssClass); }
public async Task ValidationErrorUsesDisplayAttributeName() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <int, TestInputNumberComponent> { EditContext = new EditContext(model), ValueExpression = () => model.SomeNumber, AdditionalAttributes = new Dictionary <string, object> { { "DisplayName", "Some number" } } }; var fieldIdentifier = FieldIdentifier.Create(() => model.SomeNumber); var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act await inputComponent.SetCurrentValueAsStringAsync("notANumber"); // Assert var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier); Assert.NotEmpty(validationMessages); Assert.Contains("The Some number field must be a number.", validationMessages); }
public void CanCreateFromExpression_MemberOfConstantExpression() { var fieldIdentifier = FieldIdentifier.Create(() => StringPropertyOnThisClass); Assert.Same(this, fieldIdentifier.Model); Assert.Equal(nameof(StringPropertyOnThisClass), fieldIdentifier.FieldName); }
/// <inheritdoc /> protected override void OnParametersSet() { if (CurrentEditContext == null) { throw new InvalidOperationException($"{GetType()} requires a cascading parameter " + $"of type {nameof(EditContext)}. For example, you can use {GetType()} inside " + $"an {nameof(EditForm)}."); } if (For == null) // Not possible except if you manually specify T { throw new InvalidOperationException($"{GetType()} requires a value for the " + $"{nameof(For)} parameter."); } else if (For != _previousFieldAccessor) { _fieldIdentifier = FieldIdentifier.Create(For); _previousFieldAccessor = For; } if (CurrentEditContext != _previousEditContext) { DetachValidationStateChangedListener(); CurrentEditContext.OnValidationStateChanged += _validationStateChangedHandler; _previousEditContext = CurrentEditContext; } }
public async Task AriaAttributeIsRenderedWhenTheValidationStateIsInvalidOnFirstRender() { // Arrange// Arrange var model = new TestModel(); var invalidContext = new EditContext(model); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = invalidContext, ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var messageStore = new ValidationMessageStore(invalidContext); messageStore.Add(fieldIdentifier, "Test error message"); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid 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.Equal(1, component.AdditionalAttributes.Count); //Check for "true" see https://www.w3.org/TR/wai-aria-1.1/#aria-invalid Assert.Equal("true", component.AdditionalAttributes["aria-invalid"]); }
public void CannotCreateFromExpression_NonMember() { var ex = Assert.Throws <ArgumentException>(() => FieldIdentifier.Create(() => new TestModel())); Assert.Equal($"The provided expression contains a NewExpression which is not supported. {nameof(FieldIdentifier)} only supports simple member accessors (fields, properties) of an object.", ex.Message); }
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.CssClass); // no Class was specified // Act/Assert: Modify the field rootComponent.EditContext.NotifyFieldChanged(fieldIdentifier); 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.CssClass); // Act/Assert: Clear the modification flag rootComponent.EditContext.MarkAsUnmodified(fieldIdentifier); Assert.Equal("invalid", inputComponent.CssClass); // Act/Assert: Make it valid messages.Clear(); Assert.Equal("valid", inputComponent.CssClass); }
public async Task UserSpecifiedAriaValueIsNotChangedIfInvalid() { // Arrange// Arrange var model = new TestModel(); var invalidContext = new EditContext(model); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = invalidContext, ValueExpression = () => model.StringProperty }; rootComponent.AdditionalAttributes = new Dictionary <string, object>(); rootComponent.AdditionalAttributes["aria-invalid"] = "userSpecifiedValue"; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var messageStore = new ValidationMessageStore(invalidContext); messageStore.Add(fieldIdentifier, "Test error message"); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid 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.Equal(1, component.AdditionalAttributes.Count); Assert.Equal("userSpecifiedValue", component.AdditionalAttributes["aria-invalid"]); }
public async Task ValidationErrorUsesDisplayAttributeName() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <DateTime, TestInputDateComponent> { EditContext = new EditContext(model), ValueExpression = () => model.DateProperty, AdditionalAttributes = new Dictionary <string, object> { { "DisplayName", "Date property" } } }; var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty); var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act await inputComponent.SetCurrentValueAsStringAsync("invalidDate"); // Assert var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier); Assert.NotEmpty(validationMessages); Assert.Contains("The Date property field must be a date.", validationMessages); }
public async Task ParsesCurrentValueAsStringWhenChanged_Valid() { // Arrange var model = new TestModel(); var valueChangedArgs = new List <DateTime>(); var rootComponent = new TestInputHostComponent <DateTime, TestDateInputComponent> { EditContext = new EditContext(model), ValueChanged = valueChangedArgs.Add, ValueExpression = () => model.DateProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty); var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); var numValidationStateChanges = 0; rootComponent.EditContext.OnValidationStateChanged += (sender, eventArgs) => { numValidationStateChanges++; }; // Act await inputComponent.SetCurrentValueAsStringAsync("1991/11/20"); // Assert var receivedParsedValue = valueChangedArgs.Single(); Assert.Equal(1991, receivedParsedValue.Year); Assert.Equal(11, receivedParsedValue.Month); Assert.Equal(20, receivedParsedValue.Day); Assert.True(rootComponent.EditContext.IsModified(fieldIdentifier)); Assert.Empty(rootComponent.EditContext.GetValidationMessages(fieldIdentifier)); Assert.Equal(0, numValidationStateChanges); }
public async Task RespondsToValidationStateChangeNotifications() { // 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 rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid var batch1 = renderer.Batches.Single(); var componentFrame1 = batch1.GetComponentFrames <TestInputComponent <string> >().Single(); var inputComponentId = componentFrame1.ComponentId; var component = (TestInputComponent <string>)componentFrame1.Component; Assert.Equal("valid", component.CssClass); // Act: update the field state in the EditContext and notify var messageStore = new ValidationMessageStore(rootComponent.EditContext); messageStore.Add(fieldIdentifier, "Some message"); 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("invalid", component.CssClass); }
public void CanCreateFromExpression_Field() { var model = new TestModel(); var fieldIdentifier = FieldIdentifier.Create(() => model.StringField); Assert.Same(model, fieldIdentifier.Model); Assert.Equal(nameof(model.StringField), fieldIdentifier.FieldName); }
public void CanCreateFromExpression_MemberOfObjectWithCast() { var model = new TestModel(); var fieldIdentifier = FieldIdentifier.Create(() => ((TestModel)(object)model).StringField); Assert.Same(model, fieldIdentifier.Model); Assert.Equal(nameof(TestModel.StringField), fieldIdentifier.FieldName); }
public void CanCreateFromExpression_MemberOfChildObject() { var parentModel = new ParentModel { Child = new TestModel() }; var fieldIdentifier = FieldIdentifier.Create(() => parentModel.Child.StringField); Assert.Same(parentModel.Child, fieldIdentifier.Model); Assert.Equal(nameof(TestModel.StringField), fieldIdentifier.FieldName); }
public void CanCreateFromExpression_WithCastToObject() { // This case is needed because, if a component is declared as receiving // an Expression<Func<object>>, then any value types will be implicitly cast var model = new TestModel(); Expression <Func <object> > accessor = () => model.IntProperty; var fieldIdentifier = FieldIdentifier.Create(accessor); Assert.Same(model, fieldIdentifier.Model); Assert.Equal(nameof(model.IntProperty), fieldIdentifier.FieldName); }
public void CanCreateFromExpression_MemberOfIndexedCollectionEntry() { var models = new List <TestModel>() { null, new TestModel() }; var fieldIdentifier = FieldIdentifier.Create(() => models[1].StringField); Assert.Same(models[1], fieldIdentifier.Model); Assert.Equal(nameof(TestModel.StringField), fieldIdentifier.FieldName); }
public async Task ExposesFieldIdentifierToSubclass() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = new EditContext(model), Value = "some value", ValueExpression = () => model.StringProperty }; // Act var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); // Assert Assert.Equal(FieldIdentifier.Create(() => model.StringProperty), inputComponent.FieldIdentifier); }
/// <summary> /// Adds the messages from the specified collection for the specified field. /// </summary> /// <param name="store">The <see cref="ValidationMessageStore"/>.</param> /// <param name="accessor">Identifies the field for which to add the messages.</param> /// <param name="messages">The validation messages to be added.</param> public static void AddRange(this ValidationMessageStore store, Expression <Func <object> > accessor, IEnumerable <string> messages) => store.AddRange(FieldIdentifier.Create(accessor), messages);
/// <summary> /// Gets the current validation messages for the specified field. /// /// This method does not perform validation itself. It only returns messages determined by previous validation actions. /// </summary> /// <param name="editContext">The <see cref="EditContext"/>.</param> /// <param name="accessor">Identifies the field whose current validation messages should be returned.</param> /// <returns>The current validation messages for the specified field.</returns> public static IEnumerable <string> GetValidationMessages(this EditContext editContext, Expression <Func <object> > accessor) => editContext.GetValidationMessages(FieldIdentifier.Create(accessor));
/// <summary> /// Determines whether the specified fields in this <see cref="EditContext"/> has been modified. /// </summary> /// <param name="editContext">The <see cref="EditContext"/>.</param> /// <param name="accessor">Identifies the field whose current validation messages should be returned.</param> /// <returns>True if the field has been modified; otherwise false.</returns> public static bool IsModified(this EditContext editContext, Expression <Func <object> > accessor) => editContext.IsModified(FieldIdentifier.Create(accessor));
/// <summary> /// Adds a validation message for the specified field. /// </summary> /// <param name="accessor">Identifies the field for which to add the message.</param> /// <param name="message">The validation message.</param> public void Add(Expression <Func <object> > accessor, string message) => Add(FieldIdentifier.Create(accessor), message);
/// <summary> /// Removes all messages within this <see cref="ValidationMessageStore"/> for the specified field. /// </summary> /// <param name="accessor">Identifies the field for which to remove the messages.</param> public void Clear(Expression <Func <object> > accessor) => Clear(FieldIdentifier.Create(accessor));
/// <summary> /// Adds the messages from the specified collection for the specified field. /// </summary> /// <param name="accessor">Identifies the field for which to add the messages.</param> /// <param name="messages">The validation messages to be added.</param> public void Add(Expression <Func <object> > accessor, IEnumerable <string> messages) => Add(FieldIdentifier.Create(accessor), messages);
/// <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);
protected override void OnInitialized() { _fieldIdentifier = FieldIdentifier.Create(For); EditContext.OnValidationStateChanged += HandleValidationStateChanged; }
/// <summary> /// Gets the validation messages within this <see cref="ValidationMessageStore"/> for the specified field. /// /// To get the validation messages across all validation message stores, use <see cref="EditContext.GetValidationMessages(FieldIdentifier)"/> instead /// </summary> /// <param name="accessor">The identifier for the field.</param> /// <returns>The validation messages for the specified field within this <see cref="ValidationMessageStore"/>.</returns> public IEnumerable <string> this[Expression <Func <object> > accessor] => this[FieldIdentifier.Create(accessor)];
/// <summary> /// Removes all messages within this <see cref="ValidationMessageStore"/> for the specified field. /// </summary> /// <param name="store">The <see cref="ValidationMessageStore"/>.</param> /// <param name="accessor">Identifies the field for which to remove the messages.</param> public static void Clear(this ValidationMessageStore store, Expression <Func <object> > accessor) => store.Clear(FieldIdentifier.Create(accessor));
/// <summary> /// Gets a string that indicates the status of the specified field as a CSS class. This will include /// some combination of "modified", "valid", or "invalid", depending on the status of the field. /// </summary> /// <param name="editContext">The <see cref="EditContext"/>.</param> /// <param name="accessor">An identifier for the field.</param> /// <returns>A string that indicates the status of the field.</returns> public static string FieldCssClass <TField>(this EditContext editContext, Expression <Func <TField> > accessor) => FieldCssClass(editContext, FieldIdentifier.Create(accessor));