From b9cb841ef9c07b278cf32c230a054dcc9a526bde Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:53:17 +0900 Subject: [PATCH 01/10] fix build error --- tests/R3.Tests/FactoryTests/ReturnFrameTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/R3.Tests/FactoryTests/ReturnFrameTest.cs b/tests/R3.Tests/FactoryTests/ReturnFrameTest.cs index 5f04d0f9..ae14dd43 100644 --- a/tests/R3.Tests/FactoryTests/ReturnFrameTest.cs +++ b/tests/R3.Tests/FactoryTests/ReturnFrameTest.cs @@ -10,7 +10,7 @@ public void UnitTest() var frameProvider = new ManualFrameProvider(); var cts = new CancellationTokenSource(); - var list = Observable.ReturnUnitFrame(frameProvider, cts.Token).ToLiveList(); + var list = Observable.YieldFrame(frameProvider, cts.Token).ToLiveList(); list.AssertIsNotCompleted(); frameProvider.Advance(); @@ -82,7 +82,7 @@ public void NextFrameTest() var frameProvider = new ManualFrameProvider(); // use custom fake var cts = new CancellationTokenSource(); - var list = Observable.ReturnUnitFrame(frameProvider, cts.Token).ToLiveList(); + var list = Observable.YieldFrame(frameProvider, cts.Token).ToLiveList(); list.AssertIsNotCompleted(); // ReturnFrame run same frame. From 0c316db84bedfd92b9dcca58e53dd1d753934a4c Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:46:35 +0900 Subject: [PATCH 02/10] add AllTest --- tests/R3.Tests/OperatorTests/AllTest.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/R3.Tests/OperatorTests/AllTest.cs diff --git a/tests/R3.Tests/OperatorTests/AllTest.cs b/tests/R3.Tests/OperatorTests/AllTest.cs new file mode 100644 index 00000000..2e063e0f --- /dev/null +++ b/tests/R3.Tests/OperatorTests/AllTest.cs @@ -0,0 +1,20 @@ +namespace R3.Tests.OperatorTests; + +public class AllTest +{ + [Fact] + public async Task Positive() + { + var range = Observable.Range(1, 10); + var task = range.AllAsync(static x => x is > 0 and <= 10); + (await task).Should().BeTrue(); + } + + [Fact] + public async Task Negative() + { + var range = Observable.Range(1, 10); + var task = range.AllAsync(static x => x is > 0 and < 10); + (await task).Should().BeFalse(); + } +} From 62b75ee13283d074c305fa0b929fa1f8ab38dd43 Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:46:44 +0900 Subject: [PATCH 03/10] add AnyTest --- tests/R3.Tests/OperatorTests/AnyTest.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/R3.Tests/OperatorTests/AnyTest.cs diff --git a/tests/R3.Tests/OperatorTests/AnyTest.cs b/tests/R3.Tests/OperatorTests/AnyTest.cs new file mode 100644 index 00000000..af4e5c3c --- /dev/null +++ b/tests/R3.Tests/OperatorTests/AnyTest.cs @@ -0,0 +1,20 @@ +namespace R3.Tests.OperatorTests; + +public class AnyTest +{ + [Fact] + public async Task Positive() + { + var range = Observable.Range(1, 10); + var task = range.AnyAsync(static x => x is 5); + (await task).Should().BeTrue(); + } + + [Fact] + public async Task Negative() + { + var range = Observable.Range(1, 10); + var task = range.AnyAsync(static x => x is 11); + (await task).Should().BeFalse(); + } +} From f145cb1d4e9d3b9d6d384bc3b0a6003ccbe61c1d Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:47:14 +0900 Subject: [PATCH 04/10] add ContainsTest and fix impl --- src/R3/Operators/ContainsAsync.cs | 2 +- tests/R3.Tests/OperatorTests/ContainsTest.cs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 tests/R3.Tests/OperatorTests/ContainsTest.cs diff --git a/src/R3/Operators/ContainsAsync.cs b/src/R3/Operators/ContainsAsync.cs index 247e7631..2422b7b4 100644 --- a/src/R3/Operators/ContainsAsync.cs +++ b/src/R3/Operators/ContainsAsync.cs @@ -20,7 +20,7 @@ internal sealed class ContainsAsync(T compareValue, IEqualityComparer equa { protected override void OnNextCore(T value) { - if (!equalityComparer.Equals(value, compareValue)) + if (equalityComparer.Equals(value, compareValue)) { TrySetResult(true); } diff --git a/tests/R3.Tests/OperatorTests/ContainsTest.cs b/tests/R3.Tests/OperatorTests/ContainsTest.cs new file mode 100644 index 00000000..802754e5 --- /dev/null +++ b/tests/R3.Tests/OperatorTests/ContainsTest.cs @@ -0,0 +1,20 @@ +namespace R3.Tests.OperatorTests; + +public class ContainsTest +{ + [Fact] + public async Task Positive() + { + var range = Observable.Range(1, 10); + var task = range.ContainsAsync(5); + (await task).Should().BeTrue(); + } + + [Fact] + public async Task Negative() + { + var range = Observable.Range(1, 10); + var task = range.ContainsAsync(0); + (await task).Should().BeFalse(); + } +} From d9b2f7623e9d53ba8019262fc86c54d21865ce34 Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:47:39 +0900 Subject: [PATCH 05/10] add SequenceEqualTest and impl --- src/R3/Operators/SequenceEqualAsync.cs | 2 +- .../OperatorTests/SequenceEqualTest.cs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/R3.Tests/OperatorTests/SequenceEqualTest.cs diff --git a/src/R3/Operators/SequenceEqualAsync.cs b/src/R3/Operators/SequenceEqualAsync.cs index 47f70e0b..2f4a3e6d 100644 --- a/src/R3/Operators/SequenceEqualAsync.cs +++ b/src/R3/Operators/SequenceEqualAsync.cs @@ -4,7 +4,7 @@ public static partial class ObservableExtensions { public static Task SequenceEqualAsync(this Observable source, Observable second, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + return SequenceEqualAsync(source, second, EqualityComparer.Default, cancellationToken); } public static Task SequenceEqualAsync(this Observable source, Observable second, IEqualityComparer equalityComparer, CancellationToken cancellationToken = default) diff --git a/tests/R3.Tests/OperatorTests/SequenceEqualTest.cs b/tests/R3.Tests/OperatorTests/SequenceEqualTest.cs new file mode 100644 index 00000000..88e9fb6c --- /dev/null +++ b/tests/R3.Tests/OperatorTests/SequenceEqualTest.cs @@ -0,0 +1,24 @@ +namespace R3.Tests.OperatorTests; + +public class SequenceEqualTest +{ + [Fact] + public async Task Positive() + { + var range1 = Observable.Range(1, 10); + var range2 = Observable.Range(1, 10); + + var task = range1.SequenceEqualAsync(range2); + (await task).Should().BeTrue(); + } + + [Fact] + public async Task Negative() + { + var range1 = Observable.Range(1, 10); + var range2 = Observable.Range(1, 11); + + var task = range1.SequenceEqualAsync(range2); + (await task).Should().BeFalse(); + } +} From 5e148c98df7bb59583ca1f41528fb40927432db6 Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:47:51 +0900 Subject: [PATCH 06/10] add IsEmptyTest --- tests/R3.Tests/OperatorTests/IsEmptyTest.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/R3.Tests/OperatorTests/IsEmptyTest.cs diff --git a/tests/R3.Tests/OperatorTests/IsEmptyTest.cs b/tests/R3.Tests/OperatorTests/IsEmptyTest.cs new file mode 100644 index 00000000..6371873d --- /dev/null +++ b/tests/R3.Tests/OperatorTests/IsEmptyTest.cs @@ -0,0 +1,20 @@ +namespace R3.Tests.OperatorTests; + +public class IsEmptyTest +{ + [Fact] + public async Task Positive() + { + var range = Observable.Empty(); + var task = range.IsEmptyAsync(); + (await task).Should().BeTrue(); + } + + [Fact] + public async Task Negative() + { + var range = Observable.Return(0); + var task = range.IsEmptyAsync(); + (await task).Should().BeFalse(); + } +} From 877f2a142b9996d8e5c028b54f7546dfb5fa03e2 Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:50:00 +0900 Subject: [PATCH 07/10] add MaxByTest and fix impl --- src/R3/Operators/MaxByMinByAsync.cs | 4 +--- tests/R3.Tests/OperatorTests/MaxByTest.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 tests/R3.Tests/OperatorTests/MaxByTest.cs diff --git a/src/R3/Operators/MaxByMinByAsync.cs b/src/R3/Operators/MaxByMinByAsync.cs index 269353da..3bd7bfb1 100644 --- a/src/R3/Operators/MaxByMinByAsync.cs +++ b/src/R3/Operators/MaxByMinByAsync.cs @@ -1,4 +1,4 @@ -namespace R3; +namespace R3; public static partial class ObservableExtensions { @@ -51,8 +51,6 @@ protected override void OnNextCore(T value) latestKey = key; hasValue = true; } - - TrySetResult(value); } protected override void OnErrorResumeCore(Exception error) diff --git a/tests/R3.Tests/OperatorTests/MaxByTest.cs b/tests/R3.Tests/OperatorTests/MaxByTest.cs new file mode 100644 index 00000000..06d98126 --- /dev/null +++ b/tests/R3.Tests/OperatorTests/MaxByTest.cs @@ -0,0 +1,13 @@ +namespace R3.Tests.OperatorTests; + +public class MaxByTest +{ + [Fact] + public async Task MaxBy() + { + var items = new[] { (1, 3), (2, 2), (3, 1) }.ToObservable(); + + var task = items.MaxByAsync(static x => x.Item1); + (await task).Should().Be((3, 1)); + } +} From 0a648ed0c0c02ea672384676738f1b72d6a5e844 Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 19:50:38 +0900 Subject: [PATCH 08/10] add MinByTest and fix impl --- src/R3/Operators/MaxByMinByAsync.cs | 4 +--- tests/R3.Tests/OperatorTests/MinByTest.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 tests/R3.Tests/OperatorTests/MinByTest.cs diff --git a/src/R3/Operators/MaxByMinByAsync.cs b/src/R3/Operators/MaxByMinByAsync.cs index 3bd7bfb1..acf1ff43 100644 --- a/src/R3/Operators/MaxByMinByAsync.cs +++ b/src/R3/Operators/MaxByMinByAsync.cs @@ -1,4 +1,4 @@ -namespace R3; +namespace R3; public static partial class ObservableExtensions { @@ -101,8 +101,6 @@ protected override void OnNextCore(T value) latestKey = key; hasValue = true; } - - TrySetResult(value); } protected override void OnErrorResumeCore(Exception error) diff --git a/tests/R3.Tests/OperatorTests/MinByTest.cs b/tests/R3.Tests/OperatorTests/MinByTest.cs new file mode 100644 index 00000000..2bc1e217 --- /dev/null +++ b/tests/R3.Tests/OperatorTests/MinByTest.cs @@ -0,0 +1,13 @@ +namespace R3.Tests.OperatorTests; + +public class MinByTest +{ + [Fact] + public async Task MinBy() + { + var items = new[] { (1, 3), (2, 2), (3, 1) }.ToObservable(); + + var task = items.MinByAsync(static x => x.Item1); + (await task).Should().Be((1, 3)); + } +} From 1f2e7f02deff6bce4dbb30ca7a90e114d2a251f8 Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 20:06:14 +0900 Subject: [PATCH 09/10] add ToDictionaryTest and ToLookupTest --- .../OperatorTests/ToDictionaryTest.cs | 58 ++++++++++++++++++ tests/R3.Tests/OperatorTests/ToLookupTest.cs | 60 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 tests/R3.Tests/OperatorTests/ToDictionaryTest.cs create mode 100644 tests/R3.Tests/OperatorTests/ToLookupTest.cs diff --git a/tests/R3.Tests/OperatorTests/ToDictionaryTest.cs b/tests/R3.Tests/OperatorTests/ToDictionaryTest.cs new file mode 100644 index 00000000..7cf7339f --- /dev/null +++ b/tests/R3.Tests/OperatorTests/ToDictionaryTest.cs @@ -0,0 +1,58 @@ +namespace R3.Tests.OperatorTests; + +public class ToDictionaryTest +{ + [Fact] + public async Task ToDictionary() + { + var publisher = new Subject<(int, string)>(); + + var task = publisher.ToDictionaryAsync(static x => x.Item1); + + publisher.OnNext((1, "a")); + publisher.OnNext((2, "b")); + publisher.OnNext((3, "c")); + + task.Status.Should().Be(TaskStatus.WaitingForActivation); + + publisher.OnCompleted(); + + task.Status.Should().Be(TaskStatus.RanToCompletion); + + var expected = new Dictionary + { + [1] = (1, "a"), + [2] = (2, "b"), + [3] = (3, "c") + }; + + (await task).Should().Equal(expected); + } + + [Fact] + public async Task ToDictionaryWithElementSelector() + { + var publisher = new Subject<(int, string)>(); + + var task = publisher.ToDictionaryAsync(static x => x.Item1, static x => x.Item2.ToUpperInvariant()); + + publisher.OnNext((1, "a")); + publisher.OnNext((2, "b")); + publisher.OnNext((3, "c")); + + task.Status.Should().Be(TaskStatus.WaitingForActivation); + + publisher.OnCompleted(); + + task.Status.Should().Be(TaskStatus.RanToCompletion); + + var expected = new Dictionary + { + [1] = "A", + [2] = "B", + [3] = "C" + }; + + (await task).Should().Equal(expected); + } +} diff --git a/tests/R3.Tests/OperatorTests/ToLookupTest.cs b/tests/R3.Tests/OperatorTests/ToLookupTest.cs new file mode 100644 index 00000000..7d92138b --- /dev/null +++ b/tests/R3.Tests/OperatorTests/ToLookupTest.cs @@ -0,0 +1,60 @@ +namespace R3.Tests.OperatorTests; + +public class ToLookupTest +{ + [Fact] + public async Task ToLookup() + { + var publisher = new Subject>(); + + var task = publisher.ToLookupAsync(static x => x.Key); + + publisher.OnNext(new (1, "a")); + publisher.OnNext(new (2, "b")); + publisher.OnNext(new (3, "c")); + + task.Status.Should().Be(TaskStatus.WaitingForActivation); + + publisher.OnCompleted(); + + task.Status.Should().Be(TaskStatus.RanToCompletion); + + var expected = new Dictionary + { + [1] = "a", + [2] = "b", + [3] = "c" + } + .ToLookup(static x => x.Key); + + (await task).Should().BeEquivalentTo(expected); + } + + [Fact] + public async Task ToLookupWithElementSelector() + { + var publisher = new Subject<(int, string)>(); + + var task = publisher.ToLookupAsync(static x => x.Item1, static x => x.Item2.ToUpperInvariant()); + + publisher.OnNext((1, "a")); + publisher.OnNext((2, "b")); + publisher.OnNext((3, "c")); + + task.Status.Should().Be(TaskStatus.WaitingForActivation); + + publisher.OnCompleted(); + + task.Status.Should().Be(TaskStatus.RanToCompletion); + + var expected = new Dictionary + { + [1] = "A", + [2] = "B", + [3] = "C" + } + .ToLookup(static x => x.Key, static x => x.Value); + + (await task).Should().BeEquivalentTo(expected); + } +} From 33471be26b49fab8eff897d09882c138f46fb71d Mon Sep 17 00:00:00 2001 From: OWASHI Kazunori Date: Fri, 5 Jan 2024 20:06:34 +0900 Subject: [PATCH 10/10] add merging method tests --- .../OperatorTests/CombineLatestTest.cs | 52 ++++++++++++++++++ .../OperatorTests/WithLatestFromTest.cs | 43 +++++++++++++++ tests/R3.Tests/OperatorTests/ZipLatestTest.cs | 53 +++++++++++++++++++ tests/R3.Tests/OperatorTests/ZipTest.cs | 52 ++++++++++++++++++ tests/R3.Tests/_TestHelper.cs | 10 ++-- 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 tests/R3.Tests/OperatorTests/CombineLatestTest.cs create mode 100644 tests/R3.Tests/OperatorTests/WithLatestFromTest.cs create mode 100644 tests/R3.Tests/OperatorTests/ZipLatestTest.cs create mode 100644 tests/R3.Tests/OperatorTests/ZipTest.cs diff --git a/tests/R3.Tests/OperatorTests/CombineLatestTest.cs b/tests/R3.Tests/OperatorTests/CombineLatestTest.cs new file mode 100644 index 00000000..ce16ae48 --- /dev/null +++ b/tests/R3.Tests/OperatorTests/CombineLatestTest.cs @@ -0,0 +1,52 @@ +namespace R3.Tests.OperatorTests; + +public class CombineLatestTest +{ + [Fact] + public void CombineLatest() + { + // どちらかに値が入ってきた時点で他方の最新の値とペアにして吐き出す + + var source1 = new Subject(); + var source2 = new Subject(); + + using var list = source1.CombineLatest(source2, ValueTuple.Create).ToLiveList(); + + source1.OnNext(1); + + list.AssertEmpty(); + + source1.OnNext(2); + + list.AssertEmpty(); + + source2.OnNext("a"); + + list.AssertEqual([(2, "a")]); + + source1.OnNext(3); + + list.AssertEqual([(2, "a"), (3, "a")]); + + source2.OnNext("b"); + + list.AssertEqual([(2, "a"), (3, "a"), (3, "b")]); + + source2.OnNext("c"); + + list.AssertEqual([(2, "a"), (3, "a"), (3, "b"), (3, "c")]); + + source1.OnCompleted(); + + list.AssertIsNotCompleted(); + + source1.OnNext(4); + source2.OnNext("d"); + + list.AssertEqual([(2, "a"), (3, "a"), (3, "b"), (3, "c"), (3, "d")]); + + source2.OnCompleted(); + + list.AssertIsCompleted(); + } +} diff --git a/tests/R3.Tests/OperatorTests/WithLatestFromTest.cs b/tests/R3.Tests/OperatorTests/WithLatestFromTest.cs new file mode 100644 index 00000000..cf405244 --- /dev/null +++ b/tests/R3.Tests/OperatorTests/WithLatestFromTest.cs @@ -0,0 +1,43 @@ +namespace R3.Tests.OperatorTests; + +public class WithLatestFromTest +{ + [Fact] + public void WithLatestFrom() + { + // 最初のストリームに値が発行されたタイミングで、2 番目のストリームの最新の値を併せて出力する + + var source1 = new Subject(); + var source2 = new Subject(); + + using var list = source1.WithLatestFrom(source2, ValueTuple.Create).ToLiveList(); + + source1.OnNext(1); + + list.AssertEmpty(); + + source1.OnNext(2); + + list.AssertEmpty(); + + source2.OnNext("a"); + + list.AssertEmpty(); + + source1.OnNext(3); + + list.AssertEqual([(3, "a")]); + + source2.OnNext("b"); + + list.AssertEqual([(3, "a")]); + + source2.OnNext("c"); + + list.AssertEqual([(3, "a")]); + + source1.OnCompleted(); + + list.AssertIsCompleted(); + } +} diff --git a/tests/R3.Tests/OperatorTests/ZipLatestTest.cs b/tests/R3.Tests/OperatorTests/ZipLatestTest.cs new file mode 100644 index 00000000..f5df3654 --- /dev/null +++ b/tests/R3.Tests/OperatorTests/ZipLatestTest.cs @@ -0,0 +1,53 @@ +namespace R3.Tests.OperatorTests; + +public class ZipLatestTest +{ + [Fact] + public void ZipLatest() + { + // 2つペアになった時点で新しい方から吐き出されていく + // 新しい要素が入ってきたら古いものは捨てられる + + var source1 = new Subject(); + var source2 = new Subject(); + + using var list = source1.ZipLatest(source2, ValueTuple.Create).ToLiveList(); + + source1.OnNext(1); + + list.AssertEmpty(); + + source1.OnNext(2); + + list.AssertEmpty(); + + source2.OnNext("a"); + + list.AssertEqual([(2, "a")]); + + source1.OnNext(3); + + list.AssertEqual([(2, "a")]); + + source2.OnNext("b"); + + list.AssertEqual([(2, "a"), (3, "b")]); + + source2.OnNext("c"); + + list.AssertEqual([(2, "a"), (3, "b")]); + + source1.OnCompleted(); + + list.AssertIsNotCompleted(); + + source1.OnNext(4); + source2.OnNext("d"); + + list.AssertEqual([(2, "a"), (3, "b")]); + + source2.OnCompleted(); + + list.AssertIsCompleted(); + } +} diff --git a/tests/R3.Tests/OperatorTests/ZipTest.cs b/tests/R3.Tests/OperatorTests/ZipTest.cs new file mode 100644 index 00000000..dfcded6e --- /dev/null +++ b/tests/R3.Tests/OperatorTests/ZipTest.cs @@ -0,0 +1,52 @@ +namespace R3.Tests.OperatorTests; + +public class ZipTest +{ + [Fact] + public void Zip() + { + // 2つペアになった時点で古いほうから吐き出されていく + + var source1 = new Subject(); + var source2 = new Subject(); + + using var list = source1.Zip(source2, ValueTuple.Create).ToLiveList(); + + source1.OnNext(1); + + list.AssertEmpty(); + + source1.OnNext(2); + + list.AssertEmpty(); + + source2.OnNext("a"); + + list.AssertEqual([(1, "a")]); + + source1.OnNext(3); + + list.AssertEqual([(1, "a")]); + + source2.OnNext("b"); + + list.AssertEqual([(1, "a"), (2, "b")]); + + source2.OnNext("c"); + + list.AssertEqual([(1, "a"), (2, "b"), (3, "c")]); + + source1.OnCompleted(); + + list.AssertIsNotCompleted(); + + source1.OnNext(4); + source2.OnNext("d"); + + list.AssertEqual([(1, "a"), (2, "b"), (3, "c")]); + + source2.OnCompleted(); + + list.AssertIsCompleted(); + } +} diff --git a/tests/R3.Tests/_TestHelper.cs b/tests/R3.Tests/_TestHelper.cs index ef378a7b..ca88a85c 100644 --- a/tests/R3.Tests/_TestHelper.cs +++ b/tests/R3.Tests/_TestHelper.cs @@ -1,7 +1,4 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace R3.Tests; +namespace R3.Tests; public static class _TestHelper { @@ -20,6 +17,11 @@ public static void AssertEqual(this LiveList list, params T[][] expected } } + public static void AssertEmpty(this LiveList list) + { + list.Count.Should().Be(0); + } + public static void AssertIsCompleted(this LiveList list) { list.IsCompleted.Should().BeTrue();