diff --git a/Directory.Packages.props b/Directory.Packages.props index 05b5418..d28212f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,6 +14,7 @@ + diff --git a/tests/FSharp.Control.R3.Tests/AsyncObservable/AggregationCategoryTests.fs b/tests/FSharp.Control.R3.Tests/AsyncObservable/AggregationCategoryTests.fs new file mode 100644 index 0000000..6125b69 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/AsyncObservable/AggregationCategoryTests.fs @@ -0,0 +1,28 @@ +namespace FSharp.Control.R3.Tests.AsyncObservable + +open System.Threading.Tasks +open global.FSharp.Control +open Microsoft.VisualStudio.TestTools.UnitTesting +open global.R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Async +open FSharp.Control.R3.Tests + +[] +type AggregationCategoryTests () = + [] + member _.``aggregate should match direct AggregateAsync`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let expected = + ObservableExtensions.AggregateAsync (source, 0, (fun acc x -> acc + x), TestHelpers.cancellationToken) + let! expectedValue = expected + let! actual = source |> Observable.aggregate 0 (fun acc x -> acc + x) + Assert.AreEqual (expectedValue, actual, "Async aggregate must match direct AggregateAsync result.") + } + + [] + member _.``length should count values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = source |> Observable.length + Assert.AreEqual (4, actual, "length must return emitted value count.") + } diff --git a/tests/FSharp.Control.R3.Tests/AsyncObservable/AllSelectionCategoryTests.fs b/tests/FSharp.Control.R3.Tests/AsyncObservable/AllSelectionCategoryTests.fs new file mode 100644 index 0000000..6b463b2 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/AsyncObservable/AllSelectionCategoryTests.fs @@ -0,0 +1,28 @@ +namespace FSharp.Control.R3.Tests.AsyncObservable + +open System.Threading.Tasks +open global.FSharp.Control +open Microsoft.VisualStudio.TestTools.UnitTesting +open global.R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Async +open FSharp.Control.R3.Tests + +[] +type AllSelectionCategoryTests () = + [] + member _.``all should match direct AllAsync`` () : Task = task { + let source = TestHelpers.createObservable [| 2; 4; 6 |] + let expected = + ObservableExtensions.AllAsync (source, (fun x -> x % 2 = 0), TestHelpers.cancellationToken) + let! expectedValue = expected + let! actual = source |> Observable.all (fun x -> x % 2 = 0) + Assert.AreEqual (expectedValue, actual, "Async all must match direct AllAsync result.") + } + + [] + member _.``existsAsync should detect available values`` () : Task = task { + let source = TestHelpers.createObservable [| 1 |] + let! actual = source |> Observable.existsAsync + Assert.IsTrue (actual, "existsAsync must return true for non-empty sequence.") + } diff --git a/tests/FSharp.Control.R3.Tests/AsyncObservable/ConversionCategoryTests.fs b/tests/FSharp.Control.R3.Tests/AsyncObservable/ConversionCategoryTests.fs new file mode 100644 index 0000000..07841b7 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/AsyncObservable/ConversionCategoryTests.fs @@ -0,0 +1,25 @@ +namespace FSharp.Control.R3.Tests.AsyncObservable + +open System.Threading.Tasks +open global.FSharp.Control +open Microsoft.VisualStudio.TestTools.UnitTesting +open global.R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Async +open FSharp.Control.R3.Tests + +[] +type ConversionCategoryTests () = + [] + member _.``toArray should return all values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! actual = source |> Observable.toArray + CollectionAssert.AreEqual ([| 1; 2; 3 |], actual, "toArray must return all source values.") + } + + [] + member _.``toList should return all values as list`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! actual = source |> Observable.toList + CollectionAssert.AreEqual ([| 1; 2; 3 |], actual |> List.toArray, "toList must return all source values in order.") + } diff --git a/tests/FSharp.Control.R3.Tests/AsyncObservable/SingleElementCategoryTests.fs b/tests/FSharp.Control.R3.Tests/AsyncObservable/SingleElementCategoryTests.fs new file mode 100644 index 0000000..c153d4c --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/AsyncObservable/SingleElementCategoryTests.fs @@ -0,0 +1,18 @@ +namespace FSharp.Control.R3.Tests.AsyncObservable + +open System.Threading.Tasks +open global.FSharp.Control +open Microsoft.VisualStudio.TestTools.UnitTesting +open global.R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Async +open FSharp.Control.R3.Tests + +[] +type SingleElementCategoryTests () = + [] + member _.``firstAsync should return first value`` () : Task = task { + let source = TestHelpers.createObservable [| 9; 8 |] + let! actual = source |> Observable.firstAsync + Assert.AreEqual (9, actual, "firstAsync must return the first emitted value.") + } diff --git a/tests/FSharp.Control.R3.Tests/AsyncObservable/TransformationCategoryTests.fs b/tests/FSharp.Control.R3.Tests/AsyncObservable/TransformationCategoryTests.fs new file mode 100644 index 0000000..afd9560 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/AsyncObservable/TransformationCategoryTests.fs @@ -0,0 +1,61 @@ +namespace FSharp.Control.R3.Tests.AsyncObservable + +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Async +open FSharp.Control.R3.Tests + +[] +type TransformationCategoryTests () = + [] + member _.``mapAsync should match direct SelectAwait`` () : Task = task { + let options = ProcessingOptions.Default + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! expected = + ObservableExtensions.SelectAwait ( + source, + (fun x (_ : System.Threading.CancellationToken) -> ValueTask.FromResult (x + 1)), + options.AwaitOperation, + options.ConfigureAwait, + options.CancelOnCompleted, + options.MaxConcurrent + ) + |> TestHelpers.toArrayTask + + let! actual = + source + |> Observable.mapAsync options (fun x -> async { return x + 1 }) + |> TestHelpers.toArrayTask + + CollectionAssert.AreEqual (expected, actual, "Async mapAsync must match direct SelectAwait behavior.") + } + + [] + member _.``iter should invoke action for each value`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let mutable sum = 0 + do! source |> Observable.iter (fun x -> sum <- sum + x) + Assert.AreEqual (6, sum, "iter must invoke action for each emitted value.") + } + + [] + member _.``iterAsync should await async action for each value`` () : Task = task { + let options = ProcessingOptions.Default + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let mutable sum = 0 + do! + source + |> Observable.iterAsync options (fun x -> async { sum <- sum + x }) + Assert.AreEqual (6, sum, "iterAsync must apply asynchronous action to every value.") + } + + [] + member _.``ofAsync should emit computation result`` () : Task = task { + let! actual = + Observable.ofAsync (async { return 7 }) + |> TestHelpers.toArrayTask + + CollectionAssert.AreEqual ([| 7 |], actual, "ofAsync must emit the async computation result.") + } diff --git a/tests/FSharp.Control.R3.Tests/BuilderTests.fs b/tests/FSharp.Control.R3.Tests/BuilderTests.fs deleted file mode 100644 index 68f41c0..0000000 --- a/tests/FSharp.Control.R3.Tests/BuilderTests.fs +++ /dev/null @@ -1,53 +0,0 @@ -namespace FSharp.Control.R3.Tests - -open System -open System.Threading.Tasks -open FSharp.Control.R3.Async -open FSharp.Control.R3.Observable.Builders -open Microsoft.VisualStudio.TestTools.UnitTesting -open Swensen.Unquote - -[] -type BuilderTests () = - - [] - member _.``Test builder rxquery`` () = - - let mutable hasvisited = false - use r3Bus = new R3.Subject () - - let interesting = rxquery { - for i in r3Bus do - where (i % 2 = 0) - select i - - // Same as: - // if i % 2 = 0 then - // yield i - - } - - // No-one listens yet (vs R3.ReplaySubject - r3Bus.OnNext 2 - - use subscription = - R3.ObservableExtensions.SubscribeAwait ( - interesting, - fun i cancellationToken -> - task { - // Listen events - - hasvisited <- true - - Assert.AreEqual (4, i) - - return () - } - |> System.Threading.Tasks.ValueTask - ) - - // Publish some events, "4" should be heard - [ 3..5 ] |> List.iter r3Bus.OnNext - // Note: Query will not be awaited, that's why delay. - System.Threading.Thread.Sleep 300 - Assert.AreEqual (true, hasvisited) diff --git a/tests/FSharp.Control.R3.Tests/FSharp.Control.R3.Tests.fsproj b/tests/FSharp.Control.R3.Tests/FSharp.Control.R3.Tests.fsproj index 9c07d08..bc5ecbd 100644 --- a/tests/FSharp.Control.R3.Tests/FSharp.Control.R3.Tests.fsproj +++ b/tests/FSharp.Control.R3.Tests/FSharp.Control.R3.Tests.fsproj @@ -5,12 +5,16 @@ - - + + + + + + diff --git a/tests/FSharp.Control.R3.Tests/Observable/AggregationCategoryTests.fs b/tests/FSharp.Control.R3.Tests/Observable/AggregationCategoryTests.fs new file mode 100644 index 0000000..f0a2132 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/Observable/AggregationCategoryTests.fs @@ -0,0 +1,44 @@ +namespace FSharp.Control.R3.Tests.Observable + +open System +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Tests + +[] +type AggregationCategoryTests () = + [] + member _.``concat should append second sequence`` () : Task = task { + let first = TestHelpers.createObservable [| 1; 2 |] + let second = TestHelpers.createObservable [| 3; 4 |] + let! actual = Observable.concat first second |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 1; 2; 3; 4 |], actual, "concat must append second observable after first completes.") + } + + [] + member _.``merge should match direct R3 merge`` () : Task = task { + let source1 = TestHelpers.createObservable [| 1; 2 |] + let source2 = TestHelpers.createObservable [| 10; 20 |] + let! expected = + ObservableExtensions.Merge (source1, source2) + |> TestHelpers.toArrayTask + let! actual = + Observable.merge (source1, source2) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual (expected, actual, "merge wrapper must match direct R3 merge output.") + } + + [] + member _.``catch should continue with fallback observable`` () : Task = task { + use subject = new Subject () + let recovered = + subject + |> Observable.catch (fun (_ : exn) -> Observable.singleton 99) + let pending = TestHelpers.toArrayTask recovered + subject.OnNext 1 + subject.OnCompleted (Result.Failure (Exception ("boom"))) + let! actual = pending + CollectionAssert.AreEqual ([| 1; 99 |], actual, "catch must append fallback values after source error.") + } diff --git a/tests/FSharp.Control.R3.Tests/Observable/AllSelectionCategoryTests.fs b/tests/FSharp.Control.R3.Tests/Observable/AllSelectionCategoryTests.fs new file mode 100644 index 0000000..d8c045d --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/Observable/AllSelectionCategoryTests.fs @@ -0,0 +1,93 @@ +namespace FSharp.Control.R3.Tests.Observable + +open System +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Tests + +[] +type AllSelectionCategoryTests () = + [] + member _.``where should keep matching values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = + source + |> Observable.where (fun x -> x > 2) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 3; 4 |], actual, "where must keep values matching the predicate.") + } + + [] + member _.``filter should keep matching values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = + source + |> Observable.filter (fun x -> x % 2 = 0) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 2; 4 |], actual, "filter must keep only matching elements.") + } + + [] + member _.``choose should keep only Some values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = + source + |> FSharp.Control.R3.Observable.OptionExtensions.Observable.choose (fun x -> if x % 2 = 0 then Some (x * 10) else None) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 20; 40 |], actual, "choose must emit only mapped values from Some results.") + } + + [] + member _.``distinct should remove duplicate values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 1; 2; 2; 3 |] + let! actual = source |> Observable.distinct |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 1; 2; 3 |], actual, "distinct must emit unique values in encounter order.") + } + + [] + member _.``chunkBySize should split by fixed size`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4; 5 |] + let! actual = + source + |> Observable.chunkBySize 2 + |> TestHelpers.toArrayTask + let chunks = actual |> Array.map Seq.toArray + Assert.AreEqual (3, chunks.Length, "chunkBySize should produce expected number of chunks.") + CollectionAssert.AreEqual ([| 1; 2 |], chunks[0], "First chunk should contain first two items.") + CollectionAssert.AreEqual ([| 3; 4 |], chunks[1], "Second chunk should contain next two items.") + CollectionAssert.AreEqual ([| 5 |], chunks[2], "Third chunk should contain the remaining item.") + } + + [] + member _.``chunkBy ChunkCount should match chunkBySize`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! expectedChunks = + source + |> Observable.chunkBySize 2 + |> TestHelpers.toArrayTask + let! actualChunks = + source + |> Observable.chunkBy (ChunkCount 2) + |> TestHelpers.toArrayTask + let expected = expectedChunks |> Array.map Seq.toArray + let actual = actualChunks |> Array.map Seq.toArray + Assert.AreEqual (expected.Length, actual.Length, "chunkBy ChunkCount must produce same chunk count as chunkBySize.") + CollectionAssert.AreEqual (expected[0], actual[0], "First chunk must match chunkBySize output.") + CollectionAssert.AreEqual (expected[1], actual[1], "Second chunk must match chunkBySize output.") + } + + [] + member _.``skip should skip leading values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = source |> Observable.skip 2 |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 3; 4 |], actual, "skip must ignore the configured number of leading values.") + } + + [] + member _.``take should keep leading values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = source |> Observable.take 2 |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 1; 2 |], actual, "take must emit only the configured number of leading values.") + } diff --git a/tests/FSharp.Control.R3.Tests/Observable/ConversionCategoryTests.fs b/tests/FSharp.Control.R3.Tests/Observable/ConversionCategoryTests.fs new file mode 100644 index 0000000..a5f755d --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/Observable/ConversionCategoryTests.fs @@ -0,0 +1,38 @@ +namespace FSharp.Control.R3.Tests.Observable + +open System +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Tests + +[] +type ConversionCategoryTests () = + [] + member _.``asObservable should keep source values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! expected = source |> TestHelpers.toArrayTask + let! actual = source |> Observable.asObservable |> TestHelpers.toArrayTask + CollectionAssert.AreEqual (expected, actual, "asObservable must preserve source values.") + } + + [] + member _.``cast should convert values to target type`` () : Task = task { + let source = TestHelpers.createObservable [| box 1; box 2 |] + let! actual = + source + |> Observable.cast + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 1; 2 |], actual, "cast must convert boxed integers.") + } + + [] + member _.``ofType should keep only requested runtime type`` () : Task = task { + let source = TestHelpers.createObservable [| box 1; box "x"; box 2 |] + let! actual = + source + |> Observable.ofType + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 1; 2 |], actual, "ofType must emit only values of requested type.") + } diff --git a/tests/FSharp.Control.R3.Tests/Observable/SingleElementCategoryTests.fs b/tests/FSharp.Control.R3.Tests/Observable/SingleElementCategoryTests.fs new file mode 100644 index 0000000..d5192d4 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/Observable/SingleElementCategoryTests.fs @@ -0,0 +1,22 @@ +namespace FSharp.Control.R3.Tests.Observable + +open System +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Tests + +[] +type SingleElementCategoryTests () = + [] + member _.``singleton should emit one value`` () : Task = task { + let! actual = Observable.singleton 42 |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 42 |], actual, "singleton must emit exactly one value.") + } + + [] + member _.``empty should emit no values`` () : Task = task { + let! actual = Observable.empty () |> TestHelpers.toArrayTask + Assert.AreEqual (0, actual.Length, "empty must complete without values.") + } diff --git a/tests/FSharp.Control.R3.Tests/Observable/TransformationCategoryTests.fs b/tests/FSharp.Control.R3.Tests/Observable/TransformationCategoryTests.fs new file mode 100644 index 0000000..3de64c1 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/Observable/TransformationCategoryTests.fs @@ -0,0 +1,46 @@ +namespace FSharp.Control.R3.Tests.Observable + +open System +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Tests + +[] +type TransformationCategoryTests () = + [] + member _.``map should transform each value`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! actual = + source + |> Observable.map (fun x -> x * 10) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 10; 20; 30 |], actual, "map must transform each emitted value.") + } + + [] + member _.``bind should match SelectMany behavior`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2 |] + let! expected = + ObservableExtensions.SelectMany (source, fun x -> Observable.Return (x * 10)) + |> TestHelpers.toArrayTask + let! actual = + source + |> Observable.bind (fun x -> Observable.singleton (x * 10)) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual (expected, actual, "bind must match SelectMany behavior.") + } + + [] + member _.``mapi should pass index as first argument`` () : Task = task { + let source = TestHelpers.createObservable [| 4; 5; 6 |] + let! expected = + ObservableExtensions.Select (source, fun value index -> (index * 10) + value) + |> TestHelpers.toArrayTask + let! actual = + source + |> Observable.mapi (fun index value -> (index * 10) + value) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual (expected, actual, "mapi must pass index first and value second.") + } diff --git a/tests/FSharp.Control.R3.Tests/ObservableTests.fs b/tests/FSharp.Control.R3.Tests/ObservableTests.fs deleted file mode 100644 index e45d2ce..0000000 --- a/tests/FSharp.Control.R3.Tests/ObservableTests.fs +++ /dev/null @@ -1,65 +0,0 @@ -namespace FSharp.Control.R3.Tests - -open System -open System.Threading.Tasks -open FSharp.Control.R3.Async -open Microsoft.VisualStudio.TestTools.UnitTesting -open Swensen.Unquote - -[] -type ObservableTests () = - - [] - member _.``Test length`` () : Task = - - async { - use r3Bus = new R3.Subject () - - r3Bus.OnNext 1 - - let lengthObs = Observable.length r3Bus - - r3Bus.OnNext 2 - r3Bus.OnNext 3 - r3Bus.OnCompleted (R3.Result.Success) - - let! res = lengthObs - - Assert.AreEqual (0, res) - - } - |> Async.StartImmediateAsTask - :> Task - - - [] - member _.``Test filter`` () = - - let mutable hasvisited = false - use r3Bus = new R3.Subject () - let interesting = - r3Bus - |> FSharp.Control.R3.Observable.filter (fun x -> x % 2 = 0) - - // No-one listens yet (vs R3.ReplaySubject - r3Bus.OnNext 2 - - use subscription = - R3.ObservableExtensions.SubscribeAwait ( - interesting, - fun i cancellationToken -> - task { - // Listen events - - hasvisited <- true - - Assert.AreEqual (4, i) - - return () - } - |> System.Threading.Tasks.ValueTask - ) - - // Publish some events, "4" should be heard - [ 3..5 ] |> List.iter r3Bus.OnNext - Assert.AreEqual (true, hasvisited) diff --git a/tests/FSharp.Control.R3.Tests/ProcessingOptionsTests.fs b/tests/FSharp.Control.R3.Tests/ProcessingOptionsTests.fs new file mode 100644 index 0000000..ca94fb7 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/ProcessingOptionsTests.fs @@ -0,0 +1,57 @@ +namespace FSharp.Control.R3.Tests + +open System +open System.Threading +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Control.R3 +open R3 + +[] +type ProcessingOptionsTests () = + [] + member _.``TimeSpan should use default time provider`` () = + match ChunkConfiguration.TimeSpan (TimeSpan.FromMilliseconds 10.) with + | ChunkTimeSpan (windowTime, provider) -> + Assert.AreEqual (TimeSpan.FromMilliseconds 10., windowTime, "TimeSpan helper must keep provided window time.") + Assert.AreSame (ObservableSystem.DefaultTimeProvider, provider, "TimeSpan helper must use default time provider.") + | _ -> Assert.Fail ("TimeSpan helper must create ChunkTimeSpan configuration.") + + [] + member _.``TimeSpanCount should use default time provider`` () = + match ChunkConfiguration.TimeSpanCount (TimeSpan.FromMilliseconds 10.) 3 with + | ChunkTimeSpanCount (windowTime, windowLength, provider) -> + Assert.AreEqual (TimeSpan.FromMilliseconds 10., windowTime, "TimeSpanCount helper must keep provided window time.") + Assert.AreEqual (3, windowLength, "TimeSpanCount helper must keep provided window length.") + Assert.AreSame (ObservableSystem.DefaultTimeProvider, provider, "TimeSpanCount helper must use default time provider.") + | _ -> Assert.Fail ("TimeSpanCount helper must create ChunkTimeSpanCount configuration.") + + [] + member _.``Milliseconds should use default time provider`` () = + match ChunkConfiguration.Milliseconds 15 with + | ChunkMilliseconds (windowTime, provider) -> + Assert.AreEqual (15, windowTime, "Milliseconds helper must keep provided value.") + Assert.AreSame (ObservableSystem.DefaultTimeProvider, provider, "Milliseconds helper must use default time provider.") + | _ -> Assert.Fail ("Milliseconds helper must create ChunkMilliseconds configuration.") + + [] + member _.``MillisecondsCount should use default time provider`` () = + match ChunkConfiguration.MillisecondsCount 20 4 with + | ChunkMillisecondsCount (windowTime, windowLength, provider) -> + Assert.AreEqual (20, windowTime, "MillisecondsCount helper must keep provided window time.") + Assert.AreEqual (4, windowLength, "MillisecondsCount helper must keep provided window length.") + Assert.AreSame (ObservableSystem.DefaultTimeProvider, provider, "MillisecondsCount helper must use default time provider.") + | _ -> Assert.Fail ("MillisecondsCount helper must create ChunkMillisecondsCount configuration.") + + [] + member _.``AsyncWindow should wrap async callback`` () : Task = task { + let mutable observed = 0 + let configuration = ChunkConfiguration.AsyncWindow (fun value -> async { observed <- value }) + + match configuration with + | ChunkAsyncWindow (callback, configureAwait) -> + do! callback.Invoke (11, CancellationToken.None) + Assert.AreEqual (11, observed, "AsyncWindow helper must invoke wrapped callback with value.") + Assert.IsTrue (configureAwait, "AsyncWindow helper must set configureAwait to true.") + | _ -> Assert.Fail ("AsyncWindow helper must create ChunkAsyncWindow configuration.") + } diff --git a/tests/FSharp.Control.R3.Tests/TaskObservable/AggregationCategoryTests.fs b/tests/FSharp.Control.R3.Tests/TaskObservable/AggregationCategoryTests.fs new file mode 100644 index 0000000..968c44b --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/TaskObservable/AggregationCategoryTests.fs @@ -0,0 +1,26 @@ +namespace FSharp.Control.R3.Tests.TaskObservable + +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Task +open FSharp.Control.R3.Tests + +[] +type AggregationCategoryTests () = + [] + member _.``aggregate should match direct AggregateAsync`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! actual = + source + |> Observable.aggregate TestHelpers.cancellationToken 0 (fun acc x -> acc + x) + Assert.AreEqual (6, actual, "Task aggregate must return aggregated sum.") + } + + [] + member _.``length should count values`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3; 4 |] + let! actual = source |> Observable.length TestHelpers.cancellationToken + Assert.AreEqual (4, actual, "Task length must return source value count.") + } diff --git a/tests/FSharp.Control.R3.Tests/TaskObservable/AllSelectionCategoryTests.fs b/tests/FSharp.Control.R3.Tests/TaskObservable/AllSelectionCategoryTests.fs new file mode 100644 index 0000000..4b90160 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/TaskObservable/AllSelectionCategoryTests.fs @@ -0,0 +1,28 @@ +namespace FSharp.Control.R3.Tests.TaskObservable + +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Task +open FSharp.Control.R3.Tests + +[] +type AllSelectionCategoryTests () = + [] + member _.``all should return true when all values match`` () : Task = task { + let source = TestHelpers.createObservable [| 2; 4; 6 |] + let! actual = + source + |> Observable.all TestHelpers.cancellationToken (fun x -> x % 2 = 0) + Assert.IsTrue (actual, "Task all must return true when all elements satisfy predicate.") + } + + [] + member _.``existsAsync should detect available values`` () : Task = task { + let source = TestHelpers.createObservable [| 1 |] + let! actual = + source + |> Observable.existsAsync TestHelpers.cancellationToken + Assert.IsTrue (actual, "Task existsAsync must return true for non-empty source.") + } diff --git a/tests/FSharp.Control.R3.Tests/TaskObservable/SingleElementCategoryTests.fs b/tests/FSharp.Control.R3.Tests/TaskObservable/SingleElementCategoryTests.fs new file mode 100644 index 0000000..afa1bc7 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/TaskObservable/SingleElementCategoryTests.fs @@ -0,0 +1,19 @@ +namespace FSharp.Control.R3.Tests.TaskObservable + +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Task +open FSharp.Control.R3.Tests + +[] +type SingleElementCategoryTests () = + [] + member _.``firstAsync should return first value`` () : Task = task { + let source = TestHelpers.createObservable [| 5; 6 |] + let! actual = + source + |> Observable.firstAsync TestHelpers.cancellationToken + Assert.AreEqual (5, actual, "Task firstAsync must return first emitted value.") + } diff --git a/tests/FSharp.Control.R3.Tests/TaskObservable/TransformationCategoryTests.fs b/tests/FSharp.Control.R3.Tests/TaskObservable/TransformationCategoryTests.fs new file mode 100644 index 0000000..165a20e --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/TaskObservable/TransformationCategoryTests.fs @@ -0,0 +1,43 @@ +namespace FSharp.Control.R3.Tests.TaskObservable + +open System.Threading +open System.Threading.Tasks +open Microsoft.VisualStudio.TestTools.UnitTesting +open R3 +open FSharp.Control.R3 +open FSharp.Control.R3.Task +open FSharp.Control.R3.Tests + +[] +type TransformationCategoryTests () = + [] + member _.``mapAsync should transform each value`` () : Task = task { + let options = ProcessingOptions.Default + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let! actual = + source + |> Observable.mapAsync options (fun (_ : CancellationToken) x -> Task.FromResult (x + 1)) + |> TestHelpers.toArrayTask + CollectionAssert.AreEqual ([| 2; 3; 4 |], actual, "Task mapAsync must transform each source value.") + } + + [] + member _.``iter should invoke action for each value`` () : Task = task { + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let mutable sum = 0 + do! + source + |> Observable.iter TestHelpers.cancellationToken (fun x -> sum <- sum + x) + Assert.AreEqual (6, sum, "Task iter must invoke action for each value.") + } + + [] + member _.``iterAsync should await action for each value`` () : Task = task { + let options = ProcessingOptions.Default + let source = TestHelpers.createObservable [| 1; 2; 3 |] + let mutable sum = 0 + do! + source + |> Observable.iterAsync TestHelpers.cancellationToken options (fun (_ : CancellationToken) x -> task { sum <- sum + x }) + Assert.AreEqual (6, sum, "Task iterAsync must run action for each emitted value.") + } diff --git a/tests/FSharp.Control.R3.Tests/TestHelpers.fs b/tests/FSharp.Control.R3.Tests/TestHelpers.fs new file mode 100644 index 0000000..28f3985 --- /dev/null +++ b/tests/FSharp.Control.R3.Tests/TestHelpers.fs @@ -0,0 +1,13 @@ +module FSharp.Control.R3.Tests.TestHelpers + +open System +open System.Threading +open R3 + +let createObservable (values : 'T array) = Observable.ToObservable values + +let toArrayTask (source : Observable<'T>) = ObservableExtensions.ToArrayAsync source + +let cancellationToken = CancellationToken.None + +let stringComparer = StringComparer.OrdinalIgnoreCase