diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index a55f415..80b270a 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -22,10 +22,10 @@ jobs:
all:
if: github.run_id != 1
name: Build & Tests
- uses: dailydevops/pipelines/.github/workflows/cicd-dotnet.yml@0.14.82
+ uses: dailydevops/pipelines/.github/workflows/cicd-dotnet.yml@0.14.86
with:
enableSonarQube: true
- dotnet-logging: ${{ inputs.dotnet-logging }}
+ dotnet-logging: ${{ inputs.dotnet-logging || 'minimal' }}
dotnet-version: ${{ vars.NE_DOTNET_TARGETFRAMEWORKS }}
solution: ./FluentValue.sln
secrets: inherit
diff --git a/Directory.Build.props b/Directory.Build.props
index 435e907..4ff7342 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,8 +9,8 @@
2024
- net6.0;net8.0;net9.0
- net6.0;net7.0;net8.0;net9.0
+ net8.0;net9.0
+ net8.0;net9.0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index ea3d118..9249dfd 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,7 +5,7 @@
-
+
@@ -13,18 +13,16 @@
-
-
-
-
+
+
diff --git a/FluentValue.sln b/FluentValue.sln
index 92cefdf..9d1f2f9 100644
--- a/FluentValue.sln
+++ b/FluentValue.sln
@@ -11,8 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AE6A68C3
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.FluentValue.Tests.Unit", "tests\NetEvolve.FluentValue.Tests.Unit\NetEvolve.FluentValue.Tests.Unit.csproj", "{93D85E57-7EB9-4CDD-836E-301A58BF3312}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.FluentValue.Tests.Architecture", "tests\NetEvolve.FluentValue.Tests.Architecture\NetEvolve.FluentValue.Tests.Architecture.csproj", "{6C599754-9C81-494C-9136-98259711B6C8}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2FF9F0BF-A46C-4C74-BD17-25B38E8D85EE}"
ProjectSection(SolutionItems) = preProject
.commitlintrc = .commitlintrc
@@ -46,10 +44,6 @@ Global
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Release|Any CPU.Build.0 = Release|Any CPU
- {6C599754-9C81-494C-9136-98259711B6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6C599754-9C81-494C-9136-98259711B6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6C599754-9C81-494C-9136-98259711B6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6C599754-9C81-494C-9136-98259711B6C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -57,6 +51,5 @@ Global
GlobalSection(NestedProjects) = preSolution
{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9} = {BB2B11D1-1E37-4333-9E07-E6709C1796F5}
{93D85E57-7EB9-4CDD-836E-301A58BF3312} = {AE6A68C3-E24A-45BC-B7A6-4D527AF3BF99}
- {6C599754-9C81-494C-9136-98259711B6C8} = {AE6A68C3-E24A-45BC-B7A6-4D527AF3BF99}
EndGlobalSection
EndGlobal
diff --git a/src/NetEvolve.FluentValue/Constraints/ConstraintBase.cs b/src/NetEvolve.FluentValue/Constraints/ConstraintBase.cs
index 8176ef9..d59d957 100644
--- a/src/NetEvolve.FluentValue/Constraints/ConstraintBase.cs
+++ b/src/NetEvolve.FluentValue/Constraints/ConstraintBase.cs
@@ -1,9 +1,19 @@
namespace NetEvolve.FluentValue.Constraints;
using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Text;
using NetEvolve.FluentValue;
+[SuppressMessage("Info Code Smell", "S1133:Deprecated code should be removed", Justification = "As designed.")]
+[SuppressMessage(
+ "Blocker Code Smell",
+ "S3877:Exceptions should not be thrown from unexpected methods",
+ Justification = "As designed."
+)]
internal abstract class ConstraintBase : IConstraint
{
[ThreadStatic]
@@ -11,10 +21,26 @@ internal abstract class ConstraintBase : IConstraint
private const int DefaultCapacity = 1024;
+ ///
public abstract bool IsSatisfiedBy(object? value);
+ ///
public abstract void SetDescription(StringBuilder builder);
+ ///
+ [Obsolete("This is base `object` method that should not be called.", true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DebuggerHidden]
+ public new bool Equals(object? obj) =>
+ throw new NotSupportedException("This is base `object` method that should not be called.");
+
+ ///
+ [Obsolete("This is base `object` method that should not be called.", true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DebuggerHidden]
+ public new int GetHashCode() =>
+ throw new NotSupportedException("This is base `object` method that should not be called.");
+
public override string ToString()
{
var builder = _builder ?? new StringBuilder(capacity: DefaultCapacity);
diff --git a/src/NetEvolve.FluentValue/Constraints/DefaultConstraint.cs b/src/NetEvolve.FluentValue/Constraints/DefaultConstraint.cs
index 9ef5a12..0619ea3 100644
--- a/src/NetEvolve.FluentValue/Constraints/DefaultConstraint.cs
+++ b/src/NetEvolve.FluentValue/Constraints/DefaultConstraint.cs
@@ -2,25 +2,16 @@
using System;
using System.Text;
+using NetEvolve.FluentValue;
internal sealed class DefaultConstraint : ConstraintBase
{
public override bool IsSatisfiedBy(object? value) =>
value?.GetType() switch
{
- { IsValueType: true } valueType => GetDefault(valueType).Equals(value),
+ { IsValueType: true } valueType => TypeExtensions.GetDefault(valueType)?.Equals(value) ?? false,
_ => false,
};
public override void SetDescription(StringBuilder builder) => builder.Append(" is ");
-
- private static object GetDefault(Type value)
- {
- var underlying = Nullable.GetUnderlyingType(value);
- if (underlying is not null)
- {
- return Activator.CreateInstance(underlying)!;
- }
- return Activator.CreateInstance(value)!;
- }
}
diff --git a/src/NetEvolve.FluentValue/Constraints/NotDefaultConstraint.cs b/src/NetEvolve.FluentValue/Constraints/NotDefaultConstraint.cs
new file mode 100644
index 0000000..1ac1059
--- /dev/null
+++ b/src/NetEvolve.FluentValue/Constraints/NotDefaultConstraint.cs
@@ -0,0 +1,16 @@
+namespace NetEvolve.FluentValue.Constraints;
+
+using System.Text;
+using NetEvolve.FluentValue;
+
+internal sealed class NotDefaultConstraint : ConstraintBase
+{
+ public override bool IsSatisfiedBy(object? value) =>
+ value?.GetType() switch
+ {
+ { IsValueType: true } valueType => !TypeExtensions.GetDefault(valueType)?.Equals(value) ?? false,
+ _ => true,
+ };
+
+ public override void SetDescription(StringBuilder builder) => builder.Append(" is not ");
+}
diff --git a/src/NetEvolve.FluentValue/Constraints/NotEmptyConstraint.cs b/src/NetEvolve.FluentValue/Constraints/NotEmptyConstraint.cs
new file mode 100644
index 0000000..3741f28
--- /dev/null
+++ b/src/NetEvolve.FluentValue/Constraints/NotEmptyConstraint.cs
@@ -0,0 +1,20 @@
+namespace NetEvolve.FluentValue.Constraints;
+
+using System;
+using System.Collections;
+using System.Text;
+
+internal sealed class NotEmptyConstraint : ConstraintBase
+{
+ public override bool IsSatisfiedBy(object? value) =>
+ value switch
+ {
+ string stringValue => stringValue.Length > 0,
+ Guid guidValue => guidValue != Guid.Empty,
+ ICollection collection => collection.Count > 0,
+ IEnumerable enumerable => enumerable.GetEnumerator().MoveNext(),
+ _ => false,
+ };
+
+ public override void SetDescription(StringBuilder builder) => builder.Append(" is not ");
+}
diff --git a/src/NetEvolve.FluentValue/Constraints/NotNullConstraint.cs b/src/NetEvolve.FluentValue/Constraints/NotNullConstraint.cs
new file mode 100644
index 0000000..c5d8fcb
--- /dev/null
+++ b/src/NetEvolve.FluentValue/Constraints/NotNullConstraint.cs
@@ -0,0 +1,10 @@
+namespace NetEvolve.FluentValue.Constraints;
+
+using System.Text;
+
+internal sealed class NotNullConstraint : ConstraintBase
+{
+ public override bool IsSatisfiedBy(object? value) => value is not null;
+
+ public override void SetDescription(StringBuilder builder) => builder.Append(" is not ");
+}
diff --git a/src/NetEvolve.FluentValue/IOperator.cs b/src/NetEvolve.FluentValue/IOperator.cs
index 8489f38..6a35e04 100644
--- a/src/NetEvolve.FluentValue/IOperator.cs
+++ b/src/NetEvolve.FluentValue/IOperator.cs
@@ -138,11 +138,8 @@ IConstraint EqualTo(string compareValue, StringComparison comparison = default)
///
/// The current instance.
///
- IConstraint Matches(
-#if NET7_0_OR_GREATER
- [StringSyntax(StringSyntaxAttribute.Regex)]
-#endif
- string pattern, RegexOptions? options = null) => SetConstraint(new MatchesConstraint(pattern, options));
+ IConstraint Matches([StringSyntax(StringSyntaxAttribute.Regex)] string pattern, RegexOptions? options = null) =>
+ SetConstraint(new MatchesConstraint(pattern, options));
///
/// Appends a negation operator.
@@ -165,6 +162,21 @@ IOperator Not
}
}
+ ///
+ /// Appends a constraint that the value is the value.
+ ///
+ IConstraint NotDefault => SetConstraint(new NotDefaultConstraint());
+
+ ///
+ /// Appends a constraint that the value is empty.
+ ///
+ IConstraint NotEmpty => SetConstraint(new NotEmptyConstraint());
+
+ ///
+ /// Appends a constraint that the value is .
+ ///
+ IConstraint NotNull => SetConstraint(new NotNullConstraint());
+
///
/// Appends a constraint that the value is .
///
diff --git a/src/NetEvolve.FluentValue/Operators/AndOperator.cs b/src/NetEvolve.FluentValue/Operators/AndOperator.cs
index 383e830..893e9a6 100644
--- a/src/NetEvolve.FluentValue/Operators/AndOperator.cs
+++ b/src/NetEvolve.FluentValue/Operators/AndOperator.cs
@@ -4,8 +4,9 @@
using System.Text;
using NetEvolve.Arguments;
using NetEvolve.FluentValue;
+using NetEvolve.FluentValue.Constraints;
-internal sealed class AndOperator : OperatorBase
+internal sealed class AndOperator : ConstraintBase, IOperator
{
private readonly IConstraint _left;
private IConstraint? _right;
@@ -27,7 +28,7 @@ public override bool IsSatisfiedBy(object? value)
return _left.IsSatisfiedBy(value) && _right.IsSatisfiedBy(value);
}
- public override IConstraint SetConstraint(IConstraint constraint)
+ public IConstraint SetConstraint(IConstraint constraint)
{
Argument.ThrowIfNull(constraint);
diff --git a/src/NetEvolve.FluentValue/Operators/NotOperator.cs b/src/NetEvolve.FluentValue/Operators/NotOperator.cs
index 04cf14c..582d824 100644
--- a/src/NetEvolve.FluentValue/Operators/NotOperator.cs
+++ b/src/NetEvolve.FluentValue/Operators/NotOperator.cs
@@ -4,8 +4,9 @@
using System.Text;
using NetEvolve.Arguments;
using NetEvolve.FluentValue;
+using NetEvolve.FluentValue.Constraints;
-internal sealed class NotOperator : OperatorBase
+internal sealed class NotOperator : ConstraintBase, IOperator
{
internal IConstraint? _constraint;
@@ -21,7 +22,7 @@ public override bool IsSatisfiedBy(object? value)
return !_constraint.IsSatisfiedBy(value);
}
- public override IConstraint SetConstraint(IConstraint constraint)
+ public IConstraint SetConstraint(IConstraint constraint)
{
Argument.ThrowIfNull(constraint);
diff --git a/src/NetEvolve.FluentValue/Operators/OperatorBase.cs b/src/NetEvolve.FluentValue/Operators/OperatorBase.cs
deleted file mode 100644
index 43edaac..0000000
--- a/src/NetEvolve.FluentValue/Operators/OperatorBase.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace NetEvolve.FluentValue.Operators;
-
-using NetEvolve.FluentValue;
-using NetEvolve.FluentValue.Constraints;
-
-internal abstract class OperatorBase : ConstraintBase, IOperator
-{
- public abstract IConstraint SetConstraint(IConstraint constraint);
-}
diff --git a/src/NetEvolve.FluentValue/Operators/OrOperator.cs b/src/NetEvolve.FluentValue/Operators/OrOperator.cs
index f218b0c..365b263 100644
--- a/src/NetEvolve.FluentValue/Operators/OrOperator.cs
+++ b/src/NetEvolve.FluentValue/Operators/OrOperator.cs
@@ -4,8 +4,9 @@
using System.Text;
using NetEvolve.Arguments;
using NetEvolve.FluentValue;
+using NetEvolve.FluentValue.Constraints;
-internal sealed class OrOperator : OperatorBase
+internal sealed class OrOperator : ConstraintBase, IOperator
{
private readonly IConstraint _left;
private IConstraint? _right;
@@ -27,7 +28,7 @@ public override bool IsSatisfiedBy(object? value)
return _left.IsSatisfiedBy(value) || _right.IsSatisfiedBy(value);
}
- public override IConstraint SetConstraint(IConstraint constraint)
+ public IConstraint SetConstraint(IConstraint constraint)
{
Argument.ThrowIfNull(constraint);
diff --git a/src/NetEvolve.FluentValue/Operators/XorOperator.cs b/src/NetEvolve.FluentValue/Operators/XorOperator.cs
index c7d6110..805b4bb 100644
--- a/src/NetEvolve.FluentValue/Operators/XorOperator.cs
+++ b/src/NetEvolve.FluentValue/Operators/XorOperator.cs
@@ -4,8 +4,9 @@
using System.Text;
using NetEvolve.Arguments;
using NetEvolve.FluentValue;
+using NetEvolve.FluentValue.Constraints;
-internal sealed class XorOperator : OperatorBase
+internal sealed class XorOperator : ConstraintBase, IOperator
{
private readonly IConstraint _left;
private IConstraint? _right;
@@ -27,7 +28,7 @@ public override bool IsSatisfiedBy(object? value)
return _left.IsSatisfiedBy(value) ^ _right.IsSatisfiedBy(value);
}
- public override IConstraint SetConstraint(IConstraint constraint)
+ public IConstraint SetConstraint(IConstraint constraint)
{
Argument.ThrowIfNull(constraint);
diff --git a/src/NetEvolve.FluentValue/TypeExtensions.cs b/src/NetEvolve.FluentValue/TypeExtensions.cs
new file mode 100644
index 0000000..c8184e1
--- /dev/null
+++ b/src/NetEvolve.FluentValue/TypeExtensions.cs
@@ -0,0 +1,21 @@
+namespace NetEvolve.FluentValue;
+
+using System;
+
+internal static class TypeExtensions
+{
+ internal static object? GetDefault(this Type value)
+ {
+ if (!value.IsValueType)
+ {
+ return null;
+ }
+
+ var underlying = Nullable.GetUnderlyingType(value);
+ if (underlying is not null)
+ {
+ return null;
+ }
+ return Activator.CreateInstance(value)!;
+ }
+}
diff --git a/src/NetEvolve.FluentValue/Value.cs b/src/NetEvolve.FluentValue/Value.cs
index e109220..dbe786f 100644
--- a/src/NetEvolve.FluentValue/Value.cs
+++ b/src/NetEvolve.FluentValue/Value.cs
@@ -1,6 +1,7 @@
namespace NetEvolve.FluentValue;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using NetEvolve.FluentValue.Constraints;
using NetEvolve.FluentValue.Operators;
@@ -52,7 +53,7 @@ public static IConstraint Contains(string compareValue, StringComparison compari
public static IConstraint Contains(object? compareValue) => new ContainsConstraint(compareValue);
///
- /// Appends a constraint that the value is the default value.
+ /// Appends a constraint that the value is the value.
///
public static IConstraint Default => new DefaultConstraint();
@@ -126,10 +127,7 @@ public static IConstraint EqualTo(string compareValue, StringComparison comparis
/// The current instance.
///
public static IConstraint Matches(
-#if NET7_0_OR_GREATER
- [System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex)]
-#endif
- string pattern,
+ [StringSyntax(StringSyntaxAttribute.Regex)] string pattern,
RegexOptions? options = null
) => new MatchesConstraint(pattern, options);
@@ -138,6 +136,21 @@ public static IConstraint Matches(
///
public static IOperator Not => new NotOperator();
+ ///
+ /// Appends a constraint that the value is the value.
+ ///
+ public static IConstraint NotDefault => new NotDefaultConstraint();
+
+ ///
+ /// Appends a constraint that the value is empty.
+ ///
+ public static IConstraint NotEmpty => new NotEmptyConstraint();
+
+ ///
+ /// Appends a constraint that the value is .
+ ///
+ public static IConstraint NotNull => new NotNullConstraint();
+
///
/// Appends a constraint that the value is .
///
diff --git a/tests/NetEvolve.FluentValue.Tests.Architecture/NetEvolve.FluentValue.Tests.Architecture.csproj b/tests/NetEvolve.FluentValue.Tests.Architecture/NetEvolve.FluentValue.Tests.Architecture.csproj
deleted file mode 100644
index 512b8b5..0000000
--- a/tests/NetEvolve.FluentValue.Tests.Architecture/NetEvolve.FluentValue.Tests.Architecture.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
- $(NetEvolve_TestTargetFrameworks)
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
diff --git a/tests/NetEvolve.FluentValue.Tests.Unit/ConstraintTests.cs b/tests/NetEvolve.FluentValue.Tests.Unit/ConstraintTests.cs
index 1569e43..e0e8802 100644
--- a/tests/NetEvolve.FluentValue.Tests.Unit/ConstraintTests.cs
+++ b/tests/NetEvolve.FluentValue.Tests.Unit/ConstraintTests.cs
@@ -4,240 +4,287 @@
using System.Collections.Generic;
using System.Linq;
using NetEvolve.FluentValue;
-using Xunit;
using static System.StringComparison;
public class ConstraintTests
{
- [Fact]
- public void Value_NotNot_NoFurtherConstraint_ThrowsInvalidOperationException() =>
- _ = Assert.Throws(() => Value.Not.Not);
+#pragma warning disable TUnit0046 // TUnit0046: Return a `Func` rather than a ``
+ [Test]
+ [MethodDataSource(nameof(ChainedInvalidOperationsData))]
+ public void Value_ChainedInvalidOperations_ShouldThrowInvalidOperationException(Func funcConstraint) =>
+ _ = Assert.Throws(() =>
+ {
+ var _ = funcConstraint.Invoke();
+ });
- [Fact]
- public void Value_MultipleOperators_ThrowsInvalidOperationException() =>
- Assert.Multiple(
- () => _ = Assert.Throws(() => Value.Null.And.And),
- () => _ = Assert.Throws(() => Value.Null.And.Or),
- () => _ = Assert.Throws(() => Value.Null.And.Xor),
- () => _ = Assert.Throws(() => Value.Null.Or.And),
- () => _ = Assert.Throws(() => Value.Null.Or.Or),
- () => _ = Assert.Throws(() => Value.Null.Or.Xor),
- () => _ = Assert.Throws(() => Value.Null.Xor.And),
- () => _ = Assert.Throws(() => Value.Null.Xor.Or),
- () => _ = Assert.Throws(() => Value.Null.Xor.Xor)
- );
+ public static Func[] ChainedInvalidOperationsData() =>
+ [
+ () => Value.Not.Not,
+ () => Value.Null.And.And,
+ () => Value.Null.And.Or,
+ () => Value.Null.And.Xor,
+ () => Value.Null.Or.And,
+ () => Value.Null.Or.Or,
+ () => Value.Null.Or.Xor,
+ () => Value.Null.Xor.And,
+ () => Value.Null.Xor.Or,
+ () => Value.Null.Xor.Xor,
+ ];
+#pragma warning restore TUnit0046 // TUnit0046: Return a `Func` rather than a ``
- [Fact]
- public void Value_Contains_Object_ThrowsNotSupportedException() =>
+ [Test]
+ public void Value_WhenNewObject_ShouldThrowNotSupportedException() =>
_ = Assert.Throws(() => Value.Contains(new object()).IsSatisfiedBy(new object()));
- [Theory]
- [MemberData(nameof(InvalidConstraintData))]
+ [Test]
+ [MethodDataSource(nameof(InvalidConstraintData))]
public void Value_InvalidConstraint_ThrowsInvalidOperationException(IConstraint constraint) =>
_ = Assert.Throws(() => constraint.IsSatisfiedBy(true));
- public static TheoryData InvalidConstraintData =>
+ public static Func[] InvalidConstraintData() =>
[
- Value.Not,
- Value.Null.And,
- Value.Null.Or,
- Value.Null.Xor,
- Value.Not.Null.And,
- Value.Not.Null.Or,
- Value.Not.Null.Xor,
+ () => Value.Not,
+ () => Value.Null.And,
+ () => Value.Null.Or,
+ () => Value.Null.Xor,
+ () => Value.Not.Null.And,
+ () => Value.Not.Null.Or,
+ () => Value.Not.Null.Xor,
];
- [Theory]
- [MemberData(nameof(ConstraintValueData))]
- public void Value_Theory_Expected(bool expected, IConstraint constraint, object? value)
+ [Test]
+ [MethodDataSource(nameof(ConstraintValueData))]
+ public async Task Value_Theory_Expected(bool expected, IConstraint constraint, object? value)
{
- Assert.NotNull(constraint);
+ constraint = await Assert.That(constraint).IsNotNull();
var result = constraint.IsSatisfiedBy(value);
- Assert.Equal(expected, result);
+ _ = await Assert.That(result).IsEqualTo(expected);
}
- public static TheoryData ConstraintValueData =>
- new TheoryData
- {
+ public static Func<(bool, IConstraint, object?)>[] ConstraintValueData() =>
+ [
// .Contains
- { false, Value.Contains("Null"), null },
- { false, Value.Contains("Hallo", Ordinal), "Hello World!" },
- { true, Value.Contains("Hello"), "Hello World!" },
- { false, Value.Contains('Z', Ordinal), "Hello World!" },
- { true, Value.Contains('W'), "Hello World!" },
- {
- false,
- Value.Contains(2),
- new Dictionary { { "Hello", "World!" } }
- },
- {
- true,
- Value.Contains("Hello"),
- new Dictionary { { "Hello", "World!" } }
- },
- {
- false,
- Value.Contains(2),
- new List { "Hello", "World!" }
- },
- {
- true,
- Value.Contains("World!"),
- new List { "Hello", "World!" }
- },
- { false, Value.Contains(2), Enumerable.Range(10, 10) },
- { true, Value.Contains(11), Enumerable.Range(10, 10) },
+ () => (false, Value.Contains("Null"), null),
+ () => (false, Value.Contains("Hallo", Ordinal), "Hello World!"),
+ () => (true, Value.Contains("Hello"), "Hello World!"),
+ () => (false, Value.Contains('Z', Ordinal), "Hello World!"),
+ () => (true, Value.Contains('W'), "Hello World!"),
+ () => (false, Value.Contains(2), new Dictionary { { "Hello", "World!" } }),
+ () => (true, Value.Contains("Hello"), new Dictionary { { "Hello", "World!" } }),
+ () => (false, Value.Contains(2), new List { "Hello", "World!" }),
+ () => (true, Value.Contains("World!"), new List { "Hello", "World!" }),
+ () => (false, Value.Contains(2), Enumerable.Range(10, 10)),
+ () => (true, Value.Contains(11), Enumerable.Range(10, 10)),
// .Default
- { false, Value.Default, 1 },
- { false, Value.Default, null },
- { false, Value.Default, (int?)3 },
- { true, Value.Default, 0 },
+ () => (false, Value.Default, 1),
+ () => (false, Value.Default, null),
+ () => (false, Value.Default, (int?)3),
+ () => (true, Value.Default, 0),
// .Empty
- { false, Value.Empty, "Hello World!" },
- { true, Value.Empty, string.Empty },
- { false, Value.Empty, Guid.NewGuid() },
- { true, Value.Empty, Guid.Empty },
- {
- false,
- Value.Empty,
- new List { "Hello", "World!" }
- },
- { true, Value.Empty, new List() },
- { false, Value.Empty, Enumerable.Range(10, 10) },
- { true, Value.Empty, Enumerable.Empty() },
- { false, Value.Empty, null },
+ () => (false, Value.Empty, "Hello World!"),
+ () => (true, Value.Empty, string.Empty),
+ () => (false, Value.Empty, Guid.NewGuid()),
+ () => (true, Value.Empty, Guid.Empty),
+ () => (false, Value.Empty, new List { "Hello", "World!" }),
+ () => (true, Value.Empty, new List()),
+ () => (false, Value.Empty, Enumerable.Range(10, 10)),
+ () => (true, Value.Empty, Enumerable.Empty()),
+ () => (false, Value.Empty, null),
// .EndsWith
- { false, Value.EndsWith("Welt!"), "Hello World!" },
- { true, Value.EndsWith("World!"), "Hello World!" },
- { false, Value.EndsWith('?'), "Hello World!" },
- { true, Value.EndsWith('!'), "Hello World!" },
- { true, Value.EndsWith("world!", OrdinalIgnoreCase), "Hello World!" },
- { false, Value.EndsWith("123"), 123 },
+ () => (false, Value.EndsWith("Welt!"), "Hello World!"),
+ () => (true, Value.EndsWith("World!"), "Hello World!"),
+ () => (false, Value.EndsWith('?'), "Hello World!"),
+ () => (true, Value.EndsWith('!'), "Hello World!"),
+ () => (true, Value.EndsWith("world!", OrdinalIgnoreCase), "Hello World!"),
+ () => (false, Value.EndsWith("123"), 123),
// .EqualTo
- { false, Value.EqualTo("World!"), "Hello World!" },
- { true, Value.EqualTo("Hello World!"), "Hello World!" },
- { false, Value.EqualTo('H'), "Hello World!" },
- { true, Value.EqualTo(123456), "123456" },
- { true, Value.EqualTo("hello world!", OrdinalIgnoreCase), "Hello World!" },
- { true, Value.EqualTo(123), 123 },
- { true, Value.EqualTo(null), null },
+ () => (false, Value.EqualTo("World!"), "Hello World!"),
+ () => (true, Value.EqualTo("Hello World!"), "Hello World!"),
+ () => (false, Value.EqualTo('H'), "Hello World!"),
+ () => (true, Value.EqualTo(123456), "123456"),
+ () => (true, Value.EqualTo("hello world!", OrdinalIgnoreCase), "Hello World!"),
+ () => (true, Value.EqualTo(123), 123),
+ () => (true, Value.EqualTo(null), null),
// .Matches
- { false, Value.Matches(@"\d+"), null },
- { true, Value.Matches(@"\d+"), 123456 },
- { false, Value.Matches(@"\d+"), "Hello World!" },
- { true, Value.Matches(@"\w+\s\w+"), "Hello World!" },
+ () => (false, Value.Matches(@"\d+"), null),
+ () => (true, Value.Matches(@"\d+"), 123456),
+ () => (false, Value.Matches(@"\d+"), "Hello World!"),
+ () => (true, Value.Matches(@"\w+\s\w+"), "Hello World!"),
// .Null
- { false, Value.Null, 1 },
- { true, Value.Null, null },
+ () => (false, Value.Null, 1),
+ () => (true, Value.Null, null),
// .StartsWith
- { false, Value.StartsWith("World"), "Hello World!" },
- { false, Value.StartsWith('?'), "Hello World!" },
- { true, Value.StartsWith('H'), "Hello World!" },
- { true, Value.StartsWith("hello", OrdinalIgnoreCase), "Hello World!" },
- { false, Value.StartsWith("123"), 123 },
+ () => (false, Value.StartsWith("Welt!"), "Hello World!"),
+ () => (true, Value.StartsWith("Hello"), "Hello World!"),
+ () => (false, Value.StartsWith('?'), "Hello World!"),
+ () => (true, Value.StartsWith('H'), "Hello World!"),
+ () => (true, Value.StartsWith("hello", OrdinalIgnoreCase), "Hello World!"),
+ () => (false, Value.StartsWith("123"), 123),
// .WhiteSpace
- { false, Value.WhiteSpace, "Hello World!" },
- { false, Value.WhiteSpace, null },
- { true, Value.WhiteSpace, " " },
- { true, Value.WhiteSpace, "\t" },
- { true, Value.WhiteSpace, "\n" },
- { true, Value.WhiteSpace, '\t' },
- { true, Value.WhiteSpace, '\n' },
- { false, Value.WhiteSpace, 1 },
+ () => (false, Value.WhiteSpace, "Hello World!"),
+ () => (false, Value.WhiteSpace, null),
+ () => (true, Value.WhiteSpace, " "),
+ () => (true, Value.WhiteSpace, "\t"),
+ () => (true, Value.WhiteSpace, "\n"),
+ () => (true, Value.WhiteSpace, '\t'),
+ () => (true, Value.WhiteSpace, '\n'),
+ () => (false, Value.WhiteSpace, 1),
// .And Operators
- { false, Value.Contains("Hello", OrdinalIgnoreCase).And.Contains("Welt!", Ordinal), "Hello World!" },
- { true, Value.Contains("Hello", OrdinalIgnoreCase).And.Contains("World!", Ordinal), "Hello World!" },
+ () => (false, Value.Contains("Hello", OrdinalIgnoreCase).And.Contains("Welt!", Ordinal), "Hello World!"),
+ () => (true, Value.Contains("Hello", OrdinalIgnoreCase).And.Contains("World!", Ordinal), "Hello World!"),
// .Or Operators
- { false, Value.Null.Or.Empty, "Hello World!" },
- { true, Value.Null.Or.Empty, null },
- { true, Value.Null.Or.Empty, string.Empty },
+ () => (false, Value.Null.Or.Empty, "Hello World!"),
+ () => (true, Value.Null.Or.Empty, null),
+ () => (true, Value.Null.Or.Empty, string.Empty),
// .Xor Operators
- { false, Value.Null.Xor.Empty, "Hello World!" },
- { true, Value.Null.Xor.Empty, string.Empty },
- { false, Value.Contains("Hello", OrdinalIgnoreCase).Xor.Contains("World!", Ordinal), "Hello World!" },
- { true, Value.Contains("Hello", OrdinalIgnoreCase).Xor.Not.Contains("World!", Ordinal), "Hello World!" },
+ () => (false, Value.Null.Xor.Empty, "Hello World!"),
+ () => (true, Value.Null.Xor.Empty, string.Empty),
+ () => (false, Value.Contains("Hello", OrdinalIgnoreCase).Xor.Contains("World!", Ordinal), "Hello World!"),
+ () =>
+ (true, Value.Contains("Hello", OrdinalIgnoreCase).Xor.Not.Contains("World!", Ordinal), "Hello World!"),
// .Not Operators
- { false, Value.Not.EqualTo(2), "2" },
- { false, Value.Not.EqualTo("Hello World!"), "Hello World!" },
- { false, Value.Not.Null, null },
- { false, Value.Not.Empty, string.Empty },
- { true, Value.Not.Empty, "Hello World!" },
- { true, Value.Not.Contains("Hallo", Ordinal), "Hello World!" },
- { false, Value.Not.StartsWith("Hello"), "Hello World!" },
- { false, Value.Not.EndsWith("World!"), "Hello World!" },
- { false, Value.Not.WhiteSpace, " " },
- { false, Value.Not.Matches(@"\d+"), 123456 },
- { true, Value.Not.Default, 1 },
- { true, Value.Not.Contains('Z'), "Hello World!" },
- { true, Value.Not.Contains(123), "Hello World!" },
- { true, Value.Not.StartsWith('W'), "Hello World!" },
- { true, Value.Not.EndsWith('W'), "Hello World!" },
+ () => (false, Value.Not.EqualTo(2), "2"),
+ () => (false, Value.Not.EqualTo("Hello World!"), "Hello World!"),
+ () => (false, Value.Not.Null, null),
+ () => (false, Value.Not.Empty, string.Empty),
+ () => (true, Value.Not.Empty, "Hello World!"),
+ () => (true, Value.Not.Contains("Hallo", Ordinal), "Hello World!"),
+ () => (false, Value.Not.StartsWith("Hello"), "Hello World!"),
+ () => (false, Value.Not.EndsWith("World!"), "Hello World!"),
+ () => (false, Value.Not.WhiteSpace, " "),
+ () => (false, Value.Not.Matches(@"\d+"), 123456),
+ () => (true, Value.Not.Default, 1),
+ () => (true, Value.Not.Contains('Z'), "Hello World!"),
+ () => (true, Value.Not.Contains(123), "Hello World!"),
+ () => (true, Value.Not.StartsWith('W'), "Hello World!"),
+ () => (true, Value.Not.EndsWith('W'), "Hello World!"),
// .Parenthesis
- { true, Value.Not.Parenthesis(Value.Null.Or.Empty), "Hello World!" },
- { false, Value.Not.Parenthesis(Value.Null.Or.Empty), null },
- { false, Value.Not.Parenthesis(Value.Null.Or.Empty), string.Empty },
- { false, Value.Parenthesis(Value.Null.Or.Empty), "Hello World!" },
- { true, Value.Parenthesis(Value.Null.Or.Empty), null },
- { true, Value.Parenthesis(Value.Null.Or.Empty), string.Empty },
+ () => (true, Value.Not.Parenthesis(Value.Null.Or.Empty), "Hello World!"),
+ () => (false, Value.Not.Parenthesis(Value.Null.Or.Empty), null),
+ () => (false, Value.Not.Parenthesis(Value.Null.Or.Empty), string.Empty),
+ () => (false, Value.Parenthesis(Value.Null.Or.Empty), "Hello World!"),
+ () => (true, Value.Parenthesis(Value.Null.Or.Empty), null),
+ () => (true, Value.Parenthesis(Value.Null.Or.Empty), string.Empty),
// .And.Not
- { true, Value.Not.Null.And.Not.Empty, "Hello World!" },
- { false, Value.Not.Null.And.Not.Empty, null },
- { false, Value.Not.Null.And.Not.Empty, string.Empty },
+ () => (true, Value.Not.Null.And.Not.Empty, "Hello World!"),
+ () => (false, Value.Not.Null.And.Not.Empty, null),
+ () => (false, Value.Not.Null.And.Not.Empty, string.Empty),
// .Or.Not
- { false, Value.StartsWith('A').Or.Not.StartsWith('H'), "Hello World!" },
- };
+ () => (false, Value.StartsWith('A').Or.Not.StartsWith('H'), "Hello World!"),
+ // .NotDefault
+ () => (false, Value.NotDefault, 0),
+ () => (true, Value.NotDefault, 1),
+ () => (true, Value.NotDefault, null),
+ () => (true, Value.NotDefault, "Hello World!"),
+ // .NotEmpty
+ () => (false, Value.NotEmpty, string.Empty),
+ () => (true, Value.NotEmpty, "Hello World!"),
+ () => (false, Value.NotEmpty, null),
+ () => (false, Value.NotEmpty, 1),
+ () => (false, Value.NotEmpty, new List()),
+ () => (true, Value.NotEmpty, new List { "Hello", "World!" }),
+ () => (false, Value.NotEmpty, Enumerable.Empty()),
+ () => (true, Value.NotEmpty, Enumerable.Range(10, 10)),
+ () => (true, Value.NotEmpty, Guid.NewGuid()),
+ () => (false, Value.NotEmpty, Guid.Empty),
+ // .NotNull
+ () => (false, Value.NotNull, null),
+ () => (true, Value.NotNull, "Hello World!"),
+ () => (true, Value.NotNull, 1),
+ // .Not.NotDefault
+ () => (true, Value.Not.NotDefault, 0),
+ () => (false, Value.Not.NotDefault, 1),
+ () => (false, Value.Not.NotDefault, null),
+ () => (false, Value.Not.NotDefault, "Hello World!"),
+ // .Not.NotEmpty
+ () => (true, Value.Not.NotEmpty, string.Empty),
+ () => (false, Value.Not.NotEmpty, "Hello World!"),
+ () => (true, Value.Not.NotEmpty, null),
+ () => (true, Value.Not.NotEmpty, 1),
+ () => (true, Value.Not.NotEmpty, new List()),
+ () => (false, Value.Not.NotEmpty, new List { "Hello", "World!" }),
+ () => (true, Value.Not.NotEmpty, Enumerable.Empty()),
+ () => (false, Value.Not.NotEmpty, Enumerable.Range(10, 10)),
+ // .Not.NotNull
+ () => (true, Value.Not.NotNull, null),
+ () => (false, Value.Not.NotNull, "Hello World!"),
+ () => (false, Value.Not.NotNull, 1),
+ ];
- [Theory]
- [MemberData(nameof(ConstraintToStringData))]
- public void ToString_Theory_Expected(string expected, IConstraint constraint)
+ [Test]
+ [MethodDataSource(nameof(ConstraintToStringData))]
+ public async Task ToString_Theory_Expected(string expected, IConstraint constraint)
{
- Assert.NotNull(expected);
- Assert.NotNull(constraint);
+ _ = await Assert.That(expected).IsNotNull();
+ constraint = await Assert.That(constraint).IsNotNull();
var result = constraint.ToString();
- Assert.Equal(expected, result);
+ _ = await Assert.That(result).IsEqualTo(expected);
}
- public static TheoryData ConstraintToStringData =>
- new TheoryData
- {
+ public static Func<(string, IConstraint)>[] ConstraintToStringData() =>
+ [
// .Contains
- { "\"{Value} contains `Hello`.\"", Value.Contains("Hello") },
- { "\"{Value} contains `W`.\"", Value.Contains('W') },
- { "\"{Value} contains `2`.\"", Value.Contains(2) },
+ () => ("\"{Value} contains `Hello`.\"", Value.Contains("Hello")),
+ () => ("\"{Value} contains `W`.\"", Value.Contains('W')),
+ () => ("\"{Value} contains `2`.\"", Value.Contains(2)),
// .Default
- { "\"{Value} is .\"", Value.Default },
+ () => ("\"{Value} is .\"", Value.Default),
// .Empty
- { "\"{Value} is .\"", Value.Empty },
+ () => ("\"{Value} is .\"", Value.Empty),
// .EndsWith
- { "\"{Value} ends with `World!`.\"", Value.EndsWith("World!") },
- { "\"{Value} ends with `!`.\"", Value.EndsWith('!') },
+ () => ("\"{Value} ends with `World!`.\"", Value.EndsWith("World!")),
+ () => ("\"{Value} ends with `!`.\"", Value.EndsWith('!')),
// .EqualTo
- { "\"{Value} is equal to `Hello World!`.\"", Value.EqualTo("Hello World!") },
- { "\"{Value} is equal to `123456`.\"", Value.EqualTo(123456) },
+ () => ("\"{Value} is equal to `Hello World!`.\"", Value.EqualTo("Hello World!")),
+ () => ("\"{Value} is equal to `123456`.\"", Value.EqualTo(123456)),
// .Matches
- { "\"{Value} matches `\\d+`.\"", Value.Matches(@"\d+") },
- { "\"{Value} matches `\\w+\\s\\w+`.\"", Value.Matches(@"\w+\s\w+") },
+ () => ("\"{Value} matches `\\d+`.\"", Value.Matches(@"\d+")),
+ () => ("\"{Value} matches `\\w+\\s\\w+`.\"", Value.Matches(@"\w+\s\w+")),
// .Null
- { "\"{Value} is .\"", Value.Null },
+ () => ("\"{Value} is .\"", Value.Null),
// .StartsWith
- { "\"{Value} starts with `Hello`.\"", Value.StartsWith("Hello") },
- { "\"{Value} starts with `H`.\"", Value.StartsWith('H') },
+ () => ("\"{Value} starts with `Hello`.\"", Value.StartsWith("Hello")),
+ () => ("\"{Value} starts with `H`.\"", Value.StartsWith('H')),
// .WhiteSpace
- { "\"{Value} is .\"", Value.WhiteSpace },
+ () => ("\"{Value} is .\"", Value.WhiteSpace),
// .And Operators
- { "\"{Value} contains `Hello` and contains `World!`.\"", Value.Contains("Hello").And.Contains("World!") },
+ () =>
+ ("\"{Value} contains `Hello` and contains `World!`.\"", Value.Contains("Hello").And.Contains("World!")),
// .Or Operators
- { "\"{Value} is or is .\"", Value.Null.Or.Empty },
+ () => ("\"{Value} is or is .\"", Value.Null.Or.Empty),
+ // .Xor Operators
+ () => ("\"{Value} is xor is .\"", Value.Null.Xor.Empty),
// .Not.Null
- { "\"{Value} not is .\"", Value.Not.Null },
+ () => ("\"{Value} not is .\"", Value.Not.Null),
+ // .Not.Empty
+ () => ("\"{Value} not is .\"", Value.Not.Empty),
+ // .Not.Contains
+ () => ("\"{Value} not contains `Hello`.\"", Value.Not.Contains("Hello")),
+ () => ("\"{Value} not contains `W`.\"", Value.Not.Contains('W')),
+ () => ("\"{Value} not contains `2`.\"", Value.Not.Contains(2)),
+ // .Parenthesis
+ () => ("\"{Value} not (is or is ).\"", Value.Not.Parenthesis(Value.Null.Or.Empty)),
// Extreme case
- {
- "\"{Value} not is and (starts with `H` and ends with `!`).\"",
- Value.Not.Null.And.Parenthesis(Value.StartsWith('H').And.EndsWith('!'))
- },
- };
+ () =>
+ (
+ "\"{Value} not is and (starts with `H` and ends with `!`).\"",
+ Value.Not.Null.And.Parenthesis(Value.StartsWith('H').And.EndsWith('!'))
+ ),
+ // .NotDefault
+ () => ("\"{Value} is not .\"", Value.NotDefault),
+ // .Not.NotDefault
+ () => ("\"{Value} not is not .\"", Value.Not.NotDefault),
+ // .NotEmpty
+ () => ("\"{Value} is not .\"", Value.NotEmpty),
+ // .Not.NotEmpty
+ () => ("\"{Value} not is not .\"", Value.Not.NotEmpty),
+ // .NotNull
+ () => ("\"{Value} is not .\"", Value.NotNull),
+ // .Not.NotNull
+ () => ("\"{Value} not is not .\"", Value.Not.NotNull),
+ ];
}
diff --git a/tests/NetEvolve.FluentValue.Tests.Unit/NetEvolve.FluentValue.Tests.Unit.csproj b/tests/NetEvolve.FluentValue.Tests.Unit/NetEvolve.FluentValue.Tests.Unit.csproj
index bfa19ce..37d80fa 100644
--- a/tests/NetEvolve.FluentValue.Tests.Unit/NetEvolve.FluentValue.Tests.Unit.csproj
+++ b/tests/NetEvolve.FluentValue.Tests.Unit/NetEvolve.FluentValue.Tests.Unit.csproj
@@ -1,26 +1,21 @@
+ Exe
$(NetEvolve_TestTargetFrameworks)
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
+
+
-
+
diff --git a/tests/NetEvolve.FluentValue.Tests.Unit/TypeExtensionsTests.cs b/tests/NetEvolve.FluentValue.Tests.Unit/TypeExtensionsTests.cs
new file mode 100644
index 0000000..71d5d3b
--- /dev/null
+++ b/tests/NetEvolve.FluentValue.Tests.Unit/TypeExtensionsTests.cs
@@ -0,0 +1,29 @@
+namespace NetEvolve.FluentValue.Tests.Unit;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+public class TypeExtensionsTests
+{
+ [Test]
+ [MethodDataSource(nameof(GetDefaultData))]
+ public async Task IsNullableType_ShouldReturnTrue_WhenTypeIsNullable(object? expected, Type type)
+ {
+ // Arrange & Act
+ var result = TypeExtensions.GetDefault(type);
+ // Assert
+ _ = await Assert.That(result).IsEqualTo(expected);
+ }
+
+ public static Func<(object?, Type)>[] GetDefaultData() =>
+ [
+ () => (null, typeof(int?)),
+ () => (default(int), typeof(int)),
+ () => (null, typeof(string)),
+ () => (ConsoleKey.None, typeof(ConsoleKey)),
+ () => (null, typeof(ConsoleKey?)),
+ ];
+}