diff --git a/Kyoo.Common/Utility/EnumerableExtensions.cs b/Kyoo.Common/Utility/EnumerableExtensions.cs
index e4d0379e..459d5f1c 100644
--- a/Kyoo.Common/Utility/EnumerableExtensions.cs
+++ b/Kyoo.Common/Utility/EnumerableExtensions.cs
@@ -18,13 +18,13 @@ namespace Kyoo
/// The function that will map each items
/// The type of items in
/// The type of items in the returned list
- /// The list mapped or null if the input map was null.
- /// mapper can't be null
- public static IEnumerable Map([CanBeNull] this IEnumerable self,
+ /// The list mapped.
+ /// The list or the mapper can't be null
+ public static IEnumerable Map([NotNull] this IEnumerable self,
[NotNull] Func mapper)
{
if (self == null)
- return null;
+ throw new ArgumentNullException(nameof(self));
if (mapper == null)
throw new ArgumentNullException(nameof(mapper));
@@ -46,28 +46,33 @@ namespace Kyoo
/// A map where the mapping function is asynchronous.
/// Note: might interest you.
///
- /// The IEnumerable to map. If self is null, an empty list is returned
+ /// The IEnumerable to map.
/// The asynchronous function that will map each items
/// The type of items in
/// The type of items in the returned list
/// The list mapped as an AsyncEnumerable
- /// mapper can't be null
- public static async IAsyncEnumerable MapAsync([CanBeNull] this IEnumerable self,
+ /// The list or the mapper can't be null
+ public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self,
[NotNull] Func> mapper)
{
if (self == null)
- yield break;
+ throw new ArgumentNullException(nameof(self));
if (mapper == null)
throw new ArgumentNullException(nameof(mapper));
-
- using IEnumerator enumerator = self.GetEnumerator();
- int index = 0;
- while (enumerator.MoveNext())
+ static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper)
{
- yield return await mapper(enumerator.Current, index);
- index++;
+ using IEnumerator enumerator = self.GetEnumerator();
+ int index = 0;
+
+ while (enumerator.MoveNext())
+ {
+ yield return await mapper(enumerator.Current, index);
+ index++;
+ }
}
+
+ return Generator(self, mapper);
}
///
@@ -78,19 +83,24 @@ namespace Kyoo
/// The type of items in
/// The type of items in the returned list
/// The list mapped as an AsyncEnumerable
- /// mapper can't be null
- public static async IAsyncEnumerable SelectAsync([CanBeNull] this IEnumerable self,
+ /// The list or the mapper can't be null
+ public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self,
[NotNull] Func> mapper)
{
if (self == null)
- yield break;
+ throw new ArgumentNullException(nameof(self));
if (mapper == null)
throw new ArgumentNullException(nameof(mapper));
-
- using IEnumerator enumerator = self.GetEnumerator();
- while (enumerator.MoveNext())
- yield return await mapper(enumerator.Current);
+ static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper)
+ {
+ using IEnumerator enumerator = self.GetEnumerator();
+
+ while (enumerator.MoveNext())
+ yield return await mapper(enumerator.Current);
+ }
+
+ return Generator(self, mapper);
}
///
@@ -100,16 +110,20 @@ namespace Kyoo
/// The type of items in the async list and in the returned list.
/// A task that will return a simple list
/// The list can't be null
- public static async Task> ToListAsync([NotNull] this IAsyncEnumerable self)
+ public static Task> ToListAsync([NotNull] this IAsyncEnumerable self)
{
if (self == null)
throw new ArgumentNullException(nameof(self));
-
- List ret = new();
-
- await foreach(T i in self)
- ret.Add(i);
- return ret;
+
+ static async Task> ToList(IAsyncEnumerable self)
+ {
+ List ret = new();
+ await foreach (T i in self)
+ ret.Add(i);
+ return ret;
+ }
+
+ return ToList(self);
}
///
@@ -118,22 +132,33 @@ namespace Kyoo
/// The enumerable to check
/// The action to execute is the list is empty
/// The type of items inside the list
- ///
- public static IEnumerable IfEmpty(this IEnumerable self, Action action)
+ /// The iterable and the action can't be null.
+ /// The iterator proxied, there is no dual iterations.
+ public static IEnumerable IfEmpty([NotNull] this IEnumerable self, [NotNull] Action action)
{
- using IEnumerator enumerator = self.GetEnumerator();
+ if (self == null)
+ throw new ArgumentNullException(nameof(self));
+ if (action == null)
+ throw new ArgumentNullException(nameof(action));
- if (!enumerator.MoveNext())
+ static IEnumerable Generator(IEnumerable self, Action action)
{
- action();
- yield break;
+ using IEnumerator enumerator = self.GetEnumerator();
+
+ if (!enumerator.MoveNext())
+ {
+ action();
+ yield break;
+ }
+
+ do
+ {
+ yield return enumerator.Current;
+ }
+ while (enumerator.MoveNext());
}
-
- do
- {
- yield return enumerator.Current;
- }
- while (enumerator.MoveNext());
+
+ return Generator(self, action);
}
///
diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs
index b6168251..2c97ddce 100644
--- a/Kyoo.Tests/KAssert.cs
+++ b/Kyoo.Tests/KAssert.cs
@@ -1,14 +1,41 @@
using System.Reflection;
using Xunit;
+using Xunit.Sdk;
namespace Kyoo.Tests
{
+ ///
+ /// Custom assertions used by Kyoo's tests.
+ ///
public static class KAssert
{
+ ///
+ /// Check if every property of the item is equal to the other's object.
+ ///
+ /// The value to check against
+ /// The value to check
+ /// The type to check
public static void DeepEqual(T expected, T value)
{
foreach (PropertyInfo property in typeof(T).GetProperties())
Assert.Equal(property.GetValue(expected), property.GetValue(value));
}
+
+ ///
+ /// Explicitly fail a test.
+ ///
+ public static void Fail()
+ {
+ throw new XunitException();
+ }
+
+ ///
+ /// Explicitly fail a test.
+ ///
+ /// The message that will be seen in the test report
+ public static void Fail(string message)
+ {
+ throw new XunitException(message);
+ }
}
}
\ No newline at end of file
diff --git a/Kyoo.Tests/Utility/EnumerableTests.cs b/Kyoo.Tests/Utility/EnumerableTests.cs
new file mode 100644
index 00000000..9cdd8a00
--- /dev/null
+++ b/Kyoo.Tests/Utility/EnumerableTests.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Kyoo.Tests
+{
+ public class EnumerableTests
+ {
+ [Fact]
+ public void MapTest()
+ {
+ int[] list = {1, 2, 3, 4};
+ Assert.All(list.Map((x, i) => (x, i)), x => Assert.Equal(x.x - 1, x.i));
+ Assert.Throws(() => list.Map(((Func)null)!));
+ list = null;
+ Assert.Throws(() => list!.Map((x, _) => x + 1));
+ }
+
+ [Fact]
+ public async Task MapAsyncTest()
+ {
+ int[] list = {1, 2, 3, 4};
+ await foreach((int x, int i) in list.MapAsync((x, i) => Task.FromResult((x, i))))
+ {
+ Assert.Equal(x - 1, i);
+ }
+ Assert.Throws(() => list.MapAsync(((Func>)null)!));
+ list = null;
+ Assert.Throws(() => list!.MapAsync((x, _) => Task.FromResult(x + 1)));
+ }
+
+ [Fact]
+ public async Task SelectAsyncTest()
+ {
+ int[] list = {1, 2, 3, 4};
+ int i = 2;
+ await foreach(int x in list.SelectAsync(x => Task.FromResult(x + 1)))
+ {
+ Assert.Equal(i++, x);
+ }
+ Assert.Throws(() => list.SelectAsync(((Func>)null)!));
+ list = null;
+ Assert.Throws(() => list!.SelectAsync(x => Task.FromResult(x + 1)));
+ }
+
+ [Fact]
+ public async Task ToListAsyncTest()
+ {
+ int[] expected = {1, 2, 3, 4};
+ IAsyncEnumerable list = expected.SelectAsync(Task.FromResult);
+ Assert.Equal(expected, await list.ToListAsync());
+ list = null;
+ await Assert.ThrowsAsync(() => list!.ToListAsync());
+ }
+
+ [Fact]
+ public void IfEmptyTest()
+ {
+ int[] list = {1, 2, 3, 4};
+ list = list.IfEmpty(() => KAssert.Fail("Empty action should not be triggered.")).ToArray();
+ Assert.Throws(() => list.IfEmpty(null!).ToList());
+ list = null;
+ Assert.Throws(() => list!.IfEmpty(() => {}).ToList());
+ list = Array.Empty();
+ Assert.Throws(() => list.IfEmpty(() => throw new ArgumentException()).ToList());
+ Assert.Empty(list.IfEmpty(() => {}));
+ }
+ }
+}
\ No newline at end of file