Beispiel #1
0
        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));
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        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);
        }
Beispiel #5
0
        /// <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;
            }
        }
Beispiel #6
0
        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);
        }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        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"]);
        }
Beispiel #10
0
        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);
        }
Beispiel #11
0
        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);
        }
Beispiel #12
0
        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);
        }
Beispiel #18
0
        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);
        }
Beispiel #19
0
 /// <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);
Beispiel #25
0
 /// <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;
 }
Beispiel #27
0
 /// <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)];
Beispiel #28
0
 /// <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));
Beispiel #29
0
 /// <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));