< Summary

Class:ReactiveUI.Validation.Components.ModelObservableValidationBase`1
Assembly:ReactiveUI.Validation
File(s):D:\a\1\s\src\ReactiveUI.Validation\Components\ModelObservableValidationBase.cs
Covered lines:40
Uncovered lines:10
Coverable lines:50
Total lines:195
Line coverage:80% (40 of 50)
Covered branches:7
Total branches:14
Branch coverage:50% (7 of 14)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-89.47%100%
.ctor(...)-0%100%
get_PropertyCount()-100%100%
get_Properties()-100%100%
get_Text()-100%100%
get_IsValid()-100%100%
get_ValidationStatusChange()-100%100%
Dispose()-0%100%
ContainsProperty(...)-75%50%
ContainsPropertyName(...)-100%75%
Dispose(...)-0%0%
AddProperty(...)-80%50%
Activate()-100%100%

File(s)

D:\a\1\s\src\ReactiveUI.Validation\Components\ModelObservableValidationBase.cs

#LineLine coverage
 1// Copyright (c) 2020 .NET Foundation and Contributors. All rights reserved.
 2// Licensed to the .NET Foundation under one or more agreements.
 3// The .NET Foundation licenses this file to you under the MIT license.
 4// See the LICENSE file in the project root for full license information.
 5
 6using System;
 7using System.Collections.Generic;
 8using System.Diagnostics.CodeAnalysis;
 9using System.Linq;
 10using System.Linq.Expressions;
 11using System.Reactive.Disposables;
 12using System.Reactive.Linq;
 13using System.Reactive.Subjects;
 14using ReactiveUI.Validation.Collections;
 15using ReactiveUI.Validation.Components.Abstractions;
 16using ReactiveUI.Validation.Extensions;
 17using ReactiveUI.Validation.States;
 18
 19namespace ReactiveUI.Validation.Components
 20{
 21    /// <inheritdoc cref="ReactiveObject" />
 22    /// <inheritdoc cref="IPropertyValidationComponent{TViewModel}" />
 23    /// <inheritdoc cref="IDisposable" />
 24    /// <summary>
 25    /// More generic observable for determination of validity.
 26    /// </summary>
 27    /// <remarks>
 28    /// We probably need a more 'complex' one, where the params of the validation block are
 29    /// passed through?
 30    /// Also, what about access to the view model to output the error message?.
 31    /// </remarks>
 32    public abstract class ModelObservableValidationBase<TViewModel> : ReactiveObject, IDisposable, IPropertyValidationCo
 33    {
 34        [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed by field _dis
 1035        private readonly ReplaySubject<ValidationState> _lastValidationStateSubject =
 1036            new ReplaySubject<ValidationState>(1);
 37
 38        /// <summary>
 39        /// The list of property names this validator is referencing.
 40        /// </summary>
 1041        private readonly HashSet<string> _propertyNames = new HashSet<string>();
 42
 43        // the underlying connected observable for the validation change which is published
 44        private readonly IConnectableObservable<ValidationState> _validityConnectedObservable;
 45
 1046        private readonly CompositeDisposable _disposables = new CompositeDisposable();
 47
 48        private bool _isActive;
 49
 50        private bool _isValid;
 51
 52        private ValidationText? _text;
 53
 54        /// <summary>
 55        /// Initializes a new instance of the <see cref="ModelObservableValidationBase{TViewModel}"/> class.
 56        /// </summary>
 57        /// <param name="viewModel">ViewModel instance.</param>
 58        /// <param name="validityObservable">Func to define if the viewModel is valid or not.</param>
 59        /// <param name="messageFunc">Func to define the validation error message based on the viewModel and validityObs
 60        public ModelObservableValidationBase(
 61            TViewModel viewModel,
 62            Func<TViewModel, IObservable<bool>> validityObservable,
 63            Func<TViewModel, bool, string> messageFunc)
 064            : this(viewModel, validityObservable, (vm, state) => new ValidationText(messageFunc(vm, state)))
 65        {
 066        }
 67
 68        /// <summary>
 69        /// Initializes a new instance of the <see cref="ModelObservableValidationBase{TViewModel}"/> class.
 70        /// </summary>
 71        /// <param name="viewModel">ViewModel instance.</param>
 72        /// <param name="validityObservable">Func to define if the viewModel is valid or not.</param>
 73        /// <param name="messageFunc">Func to define the validation error message based on the viewModel and validityObs
 1074        protected ModelObservableValidationBase(
 1075            TViewModel viewModel,
 1076            Func<TViewModel, IObservable<bool>> validityObservable,
 1077            Func<TViewModel, bool, ValidationText> messageFunc)
 78        {
 1079            _disposables.Add(_lastValidationStateSubject.Do(s =>
 1080            {
 2681                _isValid = s.IsValid;
 2682                _text = s.Text;
 2683            }).Subscribe());
 84
 2085            _validityConnectedObservable = Observable.Defer(() => validityObservable(viewModel))
 2686                .Select(v => new ValidationState(v, messageFunc(viewModel, v), this))
 1087                .Multicast(_lastValidationStateSubject);
 1088        }
 89
 90        /// <inheritdoc/>
 691        public int PropertyCount => _propertyNames.Count;
 92
 93        /// <inheritdoc/>
 694        public IEnumerable<string> Properties => _propertyNames.AsEnumerable();
 95
 96        /// <inheritdoc/>
 97        public ValidationText? Text
 98        {
 99            get
 100            {
 32101                Activate();
 32102                return _text;
 103            }
 104        }
 105
 106        /// <inheritdoc/>
 107        public bool IsValid
 108        {
 109            get
 110            {
 48111                Activate();
 48112                return _isValid;
 113            }
 114        }
 115
 116        /// <inheritdoc/>
 117        public IObservable<ValidationState> ValidationStatusChange
 118        {
 119            get
 120            {
 18121                Activate();
 18122                return _validityConnectedObservable;
 123            }
 124        }
 125
 126        /// <inheritdoc/>
 127        public void Dispose()
 128        {
 129            // Dispose of unmanaged resources.
 0130            Dispose(true);
 131
 132            // Suppress finalization.
 0133            GC.SuppressFinalize(this);
 0134        }
 135
 136        /// <inheritdoc/>
 137        public bool ContainsProperty<TProp>(Expression<Func<TViewModel, TProp>> property, bool exclusively = false)
 138        {
 2139            if (property is null)
 140            {
 0141                throw new ArgumentNullException(nameof(property));
 142            }
 143
 2144            var propertyName = property.Body.GetPropertyPath();
 2145            return ContainsPropertyName(propertyName, exclusively);
 146        }
 147
 148        /// <inheritdoc/>
 149        public bool ContainsPropertyName(string propertyName, bool exclusively = false)
 150        {
 6151            return exclusively
 6152                ? _propertyNames.Contains(propertyName) && _propertyNames.Count == 1
 6153                : _propertyNames.Contains(propertyName);
 154        }
 155
 156        /// <summary>
 157        /// Disposes of the managed resources.
 158        /// </summary>
 159        /// <param name="disposing">If its getting called by the <see cref="BasePropertyValidation{TViewModel}.Dispose()
 160        protected virtual void Dispose(bool disposing)
 161        {
 0162            if (disposing)
 163            {
 0164                _disposables?.Dispose();
 165            }
 0166        }
 167
 168        /// <summary>
 169        /// Adds a property to the list of this which this validation is associated with.
 170        /// </summary>
 171        /// <typeparam name="TProp">Any type.</typeparam>
 172        /// <param name="property">ViewModel property.</param>
 173        protected void AddProperty<TProp>(Expression<Func<TViewModel, TProp>> property)
 174        {
 6175            if (property is null)
 176            {
 0177                throw new ArgumentNullException(nameof(property));
 178            }
 179
 6180            var propertyName = property.Body.GetPropertyPath();
 6181            _propertyNames.Add(propertyName);
 6182        }
 183
 184        private void Activate()
 185        {
 98186            if (_isActive)
 187            {
 88188                return;
 189            }
 190
 10191            _isActive = true;
 10192            _disposables.Add(_validityConnectedObservable.Connect());
 10193        }
 194    }
 195}