diff --git a/src/Ardalis.Specification/Internals/OneOrMany.cs b/src/Ardalis.Specification/Internals/OneOrMany.cs index 80f44831..3173abc3 100644 --- a/src/Ardalis.Specification/Internals/OneOrMany.cs +++ b/src/Ardalis.Specification/Internals/OneOrMany.cs @@ -1,6 +1,6 @@ namespace Ardalis.Specification; -internal struct OneOrMany +internal struct OneOrMany where T : class { private object? _value; @@ -24,8 +24,47 @@ public void Add(T item) if (_value is T singleValue) { _value = new List(2) { singleValue, item }; + } + } + + public void AddSorted(T item, IComparer comparer) + { + if (_value is null) + { + _value = item; + return; + } + + if (comparer is null) + { + throw new ArgumentNullException(nameof(comparer), "Comparer cannot be null."); + } + + if (_value is List list) + { + var index = list.FindIndex(x => comparer.Compare(item, x) <= 0); + if (index == -1) + { + list.Add(item); + } + else + { + list.Insert(index, item); + } return; } + + if (_value is T singleValue) + { + if (comparer.Compare(item, singleValue) <= 0) + { + _value = new List(2) { item, singleValue }; + } + else + { + _value = new List(2) { singleValue, item }; + } + } } public readonly T Single @@ -41,6 +80,19 @@ public readonly T Single } } + public readonly T? SingleOrDefault + { + get + { + if (_value is T singleValue) + { + return singleValue; + } + + return null; + } + } + public readonly IEnumerable Values { get @@ -50,9 +102,9 @@ public readonly IEnumerable Values return Enumerable.Empty(); } - if (_value is List tags) + if (_value is List list) { - return tags; + return list; } if (_value is T singleValue) diff --git a/tests/Ardalis.Specification.Tests/Internals/OneOrManyTests.cs b/tests/Ardalis.Specification.Tests/Internals/OneOrManyTests.cs index ca96a4ba..60e6252b 100644 --- a/tests/Ardalis.Specification.Tests/Internals/OneOrManyTests.cs +++ b/tests/Ardalis.Specification.Tests/Internals/OneOrManyTests.cs @@ -97,6 +97,93 @@ public void Add_DoesNothing_GivenInvalidState() value.Should().BeEquivalentTo(new string[] { "foo", "bar" }); } + [Fact] + public void AddSorted_CreatesSingleItem_GivenEmptyStruct() + { + var oneOrMany = new OneOrMany(); + oneOrMany.AddSorted("foo", Comparer.Default); + + var value = Accessors.ValueOf(ref oneOrMany); + value.Should().BeOfType(); + value.Should().Be("foo"); + } + + [Fact] + public void AddSorted_InsertsInPosition_GivenSingleItem() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = "foo"; + + oneOrMany.AddSorted("bar", Comparer.Default); + + var value = Accessors.ValueOf(ref oneOrMany); + value.Should().BeOfType>(); + value.Should().BeEquivalentTo(new List { "bar", "foo" }); + } + + [Fact] + public void AddSorted_AddsToTheEnd_GivenSingleItem() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = "bar"; + + oneOrMany.AddSorted("foo", Comparer.Default); + + var value = Accessors.ValueOf(ref oneOrMany); + value.Should().BeOfType>(); + value.Should().BeEquivalentTo(new List { "bar", "foo" }); + } + + [Fact] + public void AddSorted_InsertsInPosition_GivenTwoItems() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = new List { "bar", "foo" }; + + oneOrMany.AddSorted("baz", Comparer.Default); + + var value = Accessors.ValueOf(ref oneOrMany); + value.Should().BeOfType>(); + value.Should().BeEquivalentTo(new List { "bar", "baz", "foo" }); + } + + [Fact] + public void AddSorted_AddsToTheEnd_GivenTwoItems() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = new List { "bar", "baz" }; + + oneOrMany.AddSorted("foo", Comparer.Default); + + var value = Accessors.ValueOf(ref oneOrMany); + value.Should().BeOfType>(); + value.Should().BeEquivalentTo(new List { "bar", "baz", "foo" }); + } + + [Fact] + public void AddSorted_ThrowsArgumentNullException_GivenNullComparer() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = new List { "bar", "baz" }; + + var action = () => oneOrMany.AddSorted("foo", null!); + + action.Should().Throw(); + } + + [Fact] + public void AddSorted_DoesNothing_GivenInvalidState() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = new string[] { "foo", "bar" }; + + oneOrMany.AddSorted("baz", Comparer.Default); + + var value = Accessors.ValueOf(ref oneOrMany); + value.Should().BeOfType(); + value.Should().BeEquivalentTo(new string[] { "foo", "bar" }); + } + [Fact] public void Single_ReturnsSingleItem_GivenSingleItem() { @@ -125,6 +212,32 @@ public void Single_Throws_GivenMultipleItems() action.Should().Throw(); } + [Fact] + public void SingleOrDefault_ReturnsSingleItem_GivenSingleItem() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = "foo"; + + oneOrMany.SingleOrDefault.Should().Be("foo"); + } + + [Fact] + public void SingleOrDefault_ReturnsDefault_GivenEmptyStruct() + { + var oneOrMany = new OneOrMany(); + + oneOrMany.SingleOrDefault.Should().BeNull(); + } + + [Fact] + public void SingleOrDefault_ReturnsDefault_GivenMultipleItems() + { + var oneOrMany = new OneOrMany(); + Accessors.ValueOf(ref oneOrMany) = new string[] { "foo", "bar" }; + + oneOrMany.SingleOrDefault.Should().BeNull(); + } + [Fact] public void Values_ReturnsEmpty_GivenEmptyStruct() {