Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes ReactiveValidationObject NullReferenceException and enhances performance and thread-safety of ValidationText #144

Merged
merged 12 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,35 @@ namespace ReactiveUI.Validation.Collections
{
public class ValidationText : System.Collections.Generic.IEnumerable<string>, System.Collections.IEnumerable
{
public static readonly ReactiveUI.Validation.Collections.ValidationText Empty;
public static readonly ReactiveUI.Validation.Collections.ValidationText None;
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("Calling the constructor is deprecated, please use ValidationText.Create() overloa" +
"d instead.")]
public ValidationText() { }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("Calling the constructor is deprecated, please use ValidationText.Create(IEnumerab" +
"le<ValidationText>) overload instead.")]
public ValidationText(System.Collections.Generic.IEnumerable<ReactiveUI.Validation.Collections.ValidationText> validationTexts) { }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("Calling the constructor is deprecated, please use ValidationText.Create(string) o" +
"verload instead.")]
public ValidationText(string text) { }
public int Count { get; }
public string this[int index] { get; }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("ValidationText will be made immutable in future versions, please do not use the A" +
"dd(string) method.")]
public void Add(string text) { }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("ValidationText will be made immutable in future versions, please do not use the C" +
"lear() method.")]
public void Clear() { }
public System.Collections.Generic.IEnumerator<string> GetEnumerator() { }
public string ToSingleLine(string? separator = ",") { }
public static ReactiveUI.Validation.Collections.ValidationText Create(System.Collections.Generic.IEnumerable<ReactiveUI.Validation.Collections.ValidationText> validationTexts) { }
public static ReactiveUI.Validation.Collections.ValidationText Create(System.Collections.Generic.IEnumerable<string> validationTexts) { }
public static ReactiveUI.Validation.Collections.ValidationText Create(params string[] validationTexts) { }
}
}
namespace ReactiveUI.Validation.Comparators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,35 @@ namespace ReactiveUI.Validation.Collections
{
public class ValidationText : System.Collections.Generic.IEnumerable<string>, System.Collections.IEnumerable
{
public static readonly ReactiveUI.Validation.Collections.ValidationText Empty;
public static readonly ReactiveUI.Validation.Collections.ValidationText None;
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("Calling the constructor is deprecated, please use ValidationText.Create() overloa" +
"d instead.")]
public ValidationText() { }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("Calling the constructor is deprecated, please use ValidationText.Create(IEnumerab" +
"le<ValidationText>) overload instead.")]
public ValidationText(System.Collections.Generic.IEnumerable<ReactiveUI.Validation.Collections.ValidationText> validationTexts) { }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("Calling the constructor is deprecated, please use ValidationText.Create(string) o" +
"verload instead.")]
public ValidationText(string text) { }
public int Count { get; }
public string this[int index] { get; }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("ValidationText will be made immutable in future versions, please do not use the A" +
"dd(string) method.")]
public void Add(string text) { }
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Obsolete("ValidationText will be made immutable in future versions, please do not use the C" +
"lear() method.")]
public void Clear() { }
public System.Collections.Generic.IEnumerator<string> GetEnumerator() { }
public string ToSingleLine(string? separator = ",") { }
public static ReactiveUI.Validation.Collections.ValidationText Create(System.Collections.Generic.IEnumerable<ReactiveUI.Validation.Collections.ValidationText> validationTexts) { }
public static ReactiveUI.Validation.Collections.ValidationText Create(System.Collections.Generic.IEnumerable<string> validationTexts) { }
public static ReactiveUI.Validation.Collections.ValidationText Create(params string[] validationTexts) { }
}
}
namespace ReactiveUI.Validation.Comparators
Expand Down
4 changes: 2 additions & 2 deletions src/ReactiveUI.Validation.Tests/ValidationBindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ private class CustomValidationState : IValidationState
public CustomValidationState(bool isValid, string message)
{
IsValid = isValid;
Text = new ValidationText(isValid ? string.Empty : message);
Text = isValid ? ValidationText.Empty : ValidationText.Create(message);
}

public ValidationText Text { get; }
Expand All @@ -723,4 +723,4 @@ private class ConstFormatter : IValidationTextFormatter<string>
public string Format(ValidationText validationText) => _text;
}
}
}
}
231 changes: 231 additions & 0 deletions src/ReactiveUI.Validation.Tests/ValidationTextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// Copyright (c) 2020 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;
using ReactiveUI.Validation.Collections;
using ReactiveUI.Validation.Contexts;
using Xunit;

namespace ReactiveUI.Validation.Tests
{
/// <summary>
/// Tests for <see cref="ValidationContext"/>.
/// </summary>
public class ValidationTextTests
{
/// <summary>
/// Verifies that <see cref="ValidationText.None"/> is genuinely empty.
/// </summary>
[Fact]
public void NoneValidationTextIsEmpty()
{
ValidationText vt = ValidationText.None;

Assert.Equal(0, vt.Count);

// Calling Count() checks the enumeration returns no results, unlike the Count property.
#pragma warning disable CA1829 // Use Length/Count property instead of Count() when available
Assert.Equal(0, vt.Count());
#pragma warning restore CA1829 // Use Length/Count property instead of Count() when available
Assert.Equal(string.Empty, vt.ToSingleLine());
}

/// <summary>
/// Verifies that <see cref="ValidationText.Empty"/> has a single empty item.
/// </summary>
[Fact]
public void EmptyValidationTextIsSingleEmpty()
{
ValidationText vt = ValidationText.Empty;

Assert.Equal(1, vt.Count);

// Calling Count() checks the enumeration returns no results, unlike the Count property.
#pragma warning disable CA1829 // Use Length/Count property instead of Count() when available
Assert.Equal(1, vt.Count());
#pragma warning restore CA1829 // Use Length/Count property instead of Count() when available
Assert.Same(string.Empty, vt.Single());
Assert.Equal(string.Empty, vt.ToSingleLine());
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(string[])"/> without parameters returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void ParameterlessCreateReturnsNone()
{
ValidationText vt = ValidationText.Create();

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{string})"/> with an empty enumerable <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateEmptyStringEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create((IEnumerable<string>)Array.Empty<string>());

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{ValidationText})"/> with an empty enumerable <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateEmptyValidationTextEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create(Array.Empty<ValidationText>());

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(string[])"/> with <see langword="null"/> returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateNullReturnsNone()
{
ValidationText vt = ValidationText.Create((string)null);

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{string})"/> with <see langword="null"/> enumerable returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateNullStringEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create((IEnumerable<string>)null);

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{ValidationText})"/> with <see langword="null"/> returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateNullValidationTextEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create((IEnumerable<ValidationText>)null);

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{string})"/> with an enumerable containing <see langword="null"/> returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateNullItemStringEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create((IEnumerable<string>)new string[] { null });

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{ValidationText})"/> with an enumerable containing <see cref="ValidationText.None"/> returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateNoneItemValidationTextEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create(new[] { ValidationText.None });

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{string})"/> with an enumerable containing <see cref="ValidationText.None"/> returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CreateNoneItemStringEnumerableReturnsNone()
{
ValidationText vt = ValidationText.Create(ValidationText.None);

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(string[])"/> with <see cref="string.Empty"/> returns <see cref="ValidationText.Empty"/>.
/// </summary>
[Fact]
public void CreateStringEmptyReturnsEmpty()
{
ValidationText vt = ValidationText.Create(string.Empty);

Assert.Same(ValidationText.Empty, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{string})"/> with an enumerable containing <see cref="string.Empty"/> returns <see cref="ValidationText.Empty"/>.
/// </summary>
[Fact]
public void CreateSingleStringEmptyReturnsEmpty()
{
ValidationText vt = ValidationText.Create((IEnumerable<string>)new[] { string.Empty });

Assert.Same(ValidationText.Empty, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{string})"/> with an enumerable containing <see cref="string.Empty"/> returns <see cref="ValidationText.Empty"/>.
/// </summary>
[Fact]
public void CreateValidationTextEmptyReturnsEmpty()
{
ValidationText vt = ValidationText.Create(new[] { ValidationText.Empty });

Assert.Same(ValidationText.Empty, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{ValidationText})"/> with an enumerable containing two <see cref="ValidationText.None"/> returns <see cref="ValidationText.None"/>.
/// </summary>
[Fact]
public void CombineValidationTextNoneReturnsNone()
{
ValidationText vt = ValidationText.Create(new[] { ValidationText.None, ValidationText.None });

Assert.Same(ValidationText.None, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{ValidationText})"/> with an enumerable containing <see cref="ValidationText.None"/> and <see cref="ValidationText.Empty"/> returns <see cref="ValidationText.Empty"/>.
/// </summary>
[Fact]
public void CombineValidationTextEmptyAndNoneReturnsEmpty()
{
ValidationText vt = ValidationText.Create(new[] { ValidationText.None, ValidationText.Empty });

Assert.Same(ValidationText.Empty, vt);
}

/// <summary>
/// Verifies that calling <see cref="ValidationText.Create(IEnumerable{ValidationText})"/> with an enumerable containing two <see cref="ValidationText.Empty"/>
/// returns a single <see cref="ValidationText"/> with two empty strings.
/// </summary>
[Fact]
public void CombineValidationTextEmptyReturnsTwoEmpty()
{
ValidationText vt = ValidationText.Create(new[] { ValidationText.Empty, ValidationText.Empty });

Assert.NotSame(ValidationText.Empty, vt);
Assert.Equal(2, vt.Count);

// Calling Count() checks the enumeration returns no results, unlike the Count property.
#pragma warning disable CA1829 // Use Length/Count property instead of Count() when available
Assert.Equal(2, vt.Count());
#pragma warning restore CA1829 // Use Length/Count property instead of Count() when available
Assert.Equal(string.Empty, vt[0]);
Assert.Equal(string.Empty, vt[1]);

Assert.Equal("|", vt.ToSingleLine("|"));
}
}
}
Loading