diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 22814bb..68d94ad 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -11,7 +11,7 @@
true
true
true
- 1.1.0
+ 1.1.1
.NotEmpty<T>() test extension
false
kasthack nunit xunit mstest test empty null notempty emptinness nullability
diff --git a/src/kasthack.NotEmpty.Core/AssertContext.cs b/src/kasthack.NotEmpty.Core/AssertContext.cs
index 105b5f1..5bd17a5 100644
--- a/src/kasthack.NotEmpty.Core/AssertContext.cs
+++ b/src/kasthack.NotEmpty.Core/AssertContext.cs
@@ -17,25 +17,25 @@ internal class AssertContext
public string Path => "(value)" + string.Concat(this.pathSegments.Reverse());
- public bool IsArrayElement { get; set; } = false;
+ public ElementKind ElementKind { get; set; } = Core.ElementKind.Root;
public AssertContext(AssertOptions options) => this.Options = options;
- public IDisposable EnterPath(string segment, bool isArray) => new PathContext(this, segment, isArray);
+ public IDisposable EnterPath(string segment, ElementKind elementKind) => new PathContext(this, segment, elementKind);
private struct PathContext : IDisposable
{
private readonly AssertContext context;
- private readonly bool originalIsArrayElement;
+ private readonly ElementKind originalElementKind;
private bool disposed = false;
- public PathContext(AssertContext context, string segment, bool isArray)
+ public PathContext(AssertContext context, string segment, ElementKind elementKind)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
- this.originalIsArrayElement = this.context.IsArrayElement;
+ this.originalElementKind = this.context.ElementKind;
this.context.pathSegments.Push(segment);
- this.context.IsArrayElement = isArray;
+ this.context.ElementKind = elementKind;
}
public void Dispose()
@@ -44,7 +44,7 @@ public void Dispose()
{
this.disposed = true;
this.context.pathSegments.Pop();
- this.context.IsArrayElement = this.originalIsArrayElement;
+ this.context.ElementKind = this.originalElementKind;
}
}
}
diff --git a/src/kasthack.NotEmpty.Core/AssertOptions.cs b/src/kasthack.NotEmpty.Core/AssertOptions.cs
index 6ffc505..d4d2d55 100644
--- a/src/kasthack.NotEmpty.Core/AssertOptions.cs
+++ b/src/kasthack.NotEmpty.Core/AssertOptions.cs
@@ -23,5 +23,10 @@ public class AssertOptions
/// Allows empty strings but not nulls.
///
public bool AllowEmptyCollections { get; set; } = false;
+
+ ///
+ /// Allows bool properties to be false.
+ ///
+ public bool AllowFalseBooleanProperties { get; set; } = false;
}
}
\ No newline at end of file
diff --git a/src/kasthack.NotEmpty.Core/ElementKind.cs b/src/kasthack.NotEmpty.Core/ElementKind.cs
new file mode 100644
index 0000000000..8026d9b
--- /dev/null
+++ b/src/kasthack.NotEmpty.Core/ElementKind.cs
@@ -0,0 +1,11 @@
+namespace kasthack.NotEmpty.Core
+{
+ internal enum ElementKind
+ {
+ Unknown,
+ Root,
+ Property,
+ ArrayElement,
+ DictionaryElement,
+ }
+}
\ No newline at end of file
diff --git a/src/kasthack.NotEmpty.Core/NotEmptyExtensionsBase.cs b/src/kasthack.NotEmpty.Core/NotEmptyExtensionsBase.cs
index 904ea95..79a9ff6 100644
--- a/src/kasthack.NotEmpty.Core/NotEmptyExtensionsBase.cs
+++ b/src/kasthack.NotEmpty.Core/NotEmptyExtensionsBase.cs
@@ -31,14 +31,19 @@ internal void NotEmptyInternal(T? value, AssertContext context)
var path = context.Path;
string message = GetEmptyMessage(path);
this.Assert(value is not null, message, path); // fast lane
- if (
- !(context.Options.AllowZerosInNumberArrays &&
- context.IsArrayElement &&
- value is byte or sbyte or short or ushort or char or int or uint or long or ulong or float or double or decimal or BigInteger
+ var skipDueToBeingNumberInArrayWhenAllowedByOptions = context.ElementKind == ElementKind.ArrayElement
+ && context.Options.AllowZerosInNumberArrays
+ && value is byte or sbyte or short or ushort or char or int or uint or long or ulong or float or double or decimal or BigInteger
#if NET5_0_OR_GREATER
or Half or nint or nuint
#endif
- ))
+ ;
+ var skipDueToBeingBooleanPropertyWhenAllowedByOptions = context.ElementKind == ElementKind.Property && value is bool;
+ if (!(
+ skipDueToBeingBooleanPropertyWhenAllowedByOptions
+ ||
+ skipDueToBeingNumberInArrayWhenAllowedByOptions
+ ))
{
this.Assert(!EqualityComparer.Default.Equals(default!, value!), message, path);
}
@@ -68,7 +73,7 @@ or TimeOnly _
foreach (var key in d.Keys)
{
cnt++;
- using (context.EnterPath($"[{key}]", false))
+ using (context.EnterPath($"[{key}]", ElementKind.DictionaryElement))
{
this.NotEmptyBoxed(d[key], context);
}
@@ -84,7 +89,7 @@ or TimeOnly _
var index = 0;
foreach (var item in e)
{
- using (context.EnterPath($"[{index++}]", true))
+ using (context.EnterPath($"[{index++}]", ElementKind.ArrayElement))
{
this.NotEmptyBoxed(item, context);
}
@@ -99,7 +104,7 @@ or TimeOnly _
default:
foreach (var pathValue in CachedPropertyExtractor.GetProperties(value))
{
- using (context.EnterPath($".{pathValue.Path}", false))
+ using (context.EnterPath($".{pathValue.Path}", ElementKind.Property))
{
this.NotEmptyBoxed(pathValue.Value, context);
}
diff --git a/src/kasthack.NotEmpty.Tests/NotEmptyTestBase.cs b/src/kasthack.NotEmpty.Tests/NotEmptyTestBase.cs
index 9717abb..343c860 100644
--- a/src/kasthack.NotEmpty.Tests/NotEmptyTestBase.cs
+++ b/src/kasthack.NotEmpty.Tests/NotEmptyTestBase.cs
@@ -32,6 +32,7 @@ public abstract class NotEmptyTestBase
#region Numbers
+
[Fact]
public void NotDefaultPrimitiveWorks() => this.Action(new { Value = 1 });
@@ -50,6 +51,9 @@ public abstract class NotEmptyTestBase
[Fact]
public void BoxedDefaultPrimitiveThrows() => Assert.ThrowsAny(() => this.Action(new { Value = (object)0 }));
+ [Fact]
+ public void BoxedDefaultPrimitiveThrowsAsRoot() => Assert.ThrowsAny(() => this.Action(0));
+
[Fact]
public void NullNullableThrows() => Assert.ThrowsAny(() => this.Action(new { Value = new Nullable(), }));
@@ -158,6 +162,24 @@ public struct InfiniteNestedStruct
}
#endregion
+ #region Booleans
+
+ [Fact]
+ public void FalseThrows() => Assert.ThrowsAny(() => this.Action(false));
+
+ [Fact]
+ public void TrueWorks() => this.Action(true);
+
+ [Fact]
+ public void FalseDoesntThrowWhenAllowed() => this.Action(new { Value = true }, new AssertOptions { AllowFalseBooleanProperties = true });
+
+ [Fact]
+ public void FalseThrowsWhenAllowedForDifferentKind() => Assert.ThrowsAny(() => this.Action(false, new AssertOptions { AllowFalseBooleanProperties = true }));
+
+ [Fact]
+ public void FalseThrowsWhenAllowedForDifferentKindV2() => Assert.ThrowsAny(() => this.Action(new[] { false }, new AssertOptions { AllowFalseBooleanProperties = true }));
+ #endregion
+
protected void Action(object? value, AssertOptions? options = null) => this.action(value, options);
}
}
\ No newline at end of file