Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions release-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ Release notes:

1.0.0
- adds TaskSeq.withCancellation, #167
- adds TaskSeq.replicateInfinite, replicateInfiniteAsync, replicateUntilNoneAsync, #345
- adds TaskSeq.firstOrDefault, lastOrDefault, #345
- adds TaskSeq.splitAt, #345
- adds TaskSeq.zipWith, zipWithAsync, zipWith3, zipWithAsync3, #345
- adds TaskSeq.chunkBy, chunkByAsync, #345
- adds TaskSeq.threadState, threadStateAsync, #345
- adds docs/ with fsdocs-based documentation site covering generating, transforming, consuming, combining and advanced operations

0.7.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,23 @@
<Compile Include="TaskSeq.RemoveAt.Tests.fs" />
<Compile Include="TaskSeq.Singleton.Tests.fs" />
<Compile Include="TaskSeq.Replicate.Tests.fs" />
<Compile Include="TaskSeq.ReplicateInfinite.Tests.fs" />
<Compile Include="TaskSeq.Skip.Tests.fs" />
<Compile Include="TaskSeq.SkipWhile.Tests.fs" />
<Compile Include="TaskSeq.SplitAt.Tests.fs" />
<Compile Include="TaskSeq.Tail.Tests.fs" />
<Compile Include="TaskSeq.Take.Tests.fs" />
<Compile Include="TaskSeq.TakeWhile.Tests.fs" />
<Compile Include="TaskSeq.ToXXX.Tests.fs" />
<Compile Include="TaskSeq.UpdateAt.Tests.fs" />
<Compile Include="TaskSeq.Zip.Tests.fs" />
<Compile Include="TaskSeq.ZipWith.Tests.fs" />
<Compile Include="TaskSeq.CompareWith.Tests.fs" />
<Compile Include="TaskSeq.ChunkBySize.Tests.fs" />
<Compile Include="TaskSeq.ChunkBy.Tests.fs" />
<Compile Include="TaskSeq.Windowed.Tests.fs" />
<Compile Include="TaskSeq.FirstLastDefault.Tests.fs" />
<Compile Include="TaskSeq.ThreadState.Tests.fs" />
<Compile Include="TaskSeq.Tests.CE.fs" />
<Compile Include="TaskSeq.StateTransitionBug.Tests.CE.fs" />
<Compile Include="TaskSeq.StateTransitionBug-delayed.Tests.CE.fs" />
Expand Down
135 changes: 135 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.ChunkBy.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
module TaskSeq.Tests.ChunkBy

open Xunit
open FsUnit.Xunit

open FSharp.Control

//
// TaskSeq.chunkBy
// TaskSeq.chunkByAsync
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.chunkBy id (null: TaskSeq<int>)

assertNullArg
<| fun () -> TaskSeq.chunkByAsync (fun x -> Task.fromResult x) (null: TaskSeq<int>)

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-chunkBy on empty gives empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.chunkBy id
|> verifyEmpty

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-chunkByAsync on empty gives empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.chunkByAsync (fun x -> Task.fromResult x)
|> verifyEmpty


module Functionality =
[<Fact>]
let ``TaskSeq-chunkBy groups consecutive equal elements`` () = task {
let ts = taskSeq { yield! [ 1; 1; 2; 2; 2; 3 ] }
let! result = TaskSeq.chunkBy id ts |> TaskSeq.toArrayAsync
result |> should haveLength 3
result[0] |> should equal (1, [| 1; 1 |])
result[1] |> should equal (2, [| 2; 2; 2 |])
result[2] |> should equal (3, [| 3 |])
}

[<Fact>]
let ``TaskSeq-chunkBy with all same key yields one chunk`` () = task {
let ts = taskSeq { yield! [ 5; 5; 5; 5 ] }
let! result = TaskSeq.chunkBy id ts |> TaskSeq.toArrayAsync
result |> should haveLength 1
result[0] |> should equal (5, [| 5; 5; 5; 5 |])
}

[<Fact>]
let ``TaskSeq-chunkBy with all different keys yields singleton chunks`` () = task {
let ts = taskSeq { yield! [ 1..5 ] }
let! result = TaskSeq.chunkBy id ts |> TaskSeq.toArrayAsync
result |> should haveLength 5

result
|> Array.iteri (fun i (k, arr) ->
k |> should equal (i + 1)
arr |> should equal [| i + 1 |])
}

[<Fact>]
let ``TaskSeq-chunkBy with singleton source yields one chunk`` () = task {
let ts = TaskSeq.singleton 42
let! result = TaskSeq.chunkBy id ts |> TaskSeq.toArrayAsync
result |> should haveLength 1
result[0] |> should equal (42, [| 42 |])
}

[<Fact>]
let ``TaskSeq-chunkBy uses projection key, not element`` () = task {
let ts = taskSeq {
yield "a1"
yield "a2"
yield "b1"
yield "b2"
yield "a3"
}

let! result =
TaskSeq.chunkBy (fun (s: string) -> s[0]) ts
|> TaskSeq.toArrayAsync

result |> should haveLength 3
let k0, arr0 = result[0]
k0 |> should equal 'a'
arr0 |> should equal [| "a1"; "a2" |]
let k1, arr1 = result[1]
k1 |> should equal 'b'
arr1 |> should equal [| "b1"; "b2" |]
let k2, arr2 = result[2]
k2 |> should equal 'a'
arr2 |> should equal [| "a3" |]
}

[<Fact>]
let ``TaskSeq-chunkBy does not merge non-consecutive equal keys`` () = task {
// Key alternates: 1, 2, 1, 2 — should produce 4 chunks not 2
let ts = taskSeq { yield! [ 1; 2; 1; 2 ] }
let! result = TaskSeq.chunkBy id ts |> TaskSeq.toArrayAsync
result |> should haveLength 4
}

[<Fact>]
let ``TaskSeq-chunkByAsync groups consecutive by async key`` () = task {
let ts = taskSeq { yield! [ 1; 1; 2; 3; 3 ] }

let! result =
TaskSeq.chunkByAsync (fun x -> Task.fromResult (x % 2 = 0)) ts
|> TaskSeq.toArrayAsync
// odd, even, odd -> 3 chunks
result |> should haveLength 3
let k0, arr0 = result[0]
k0 |> should equal false
arr0 |> should equal [| 1; 1 |]
let k1, arr1 = result[1]
k1 |> should equal true
arr1 |> should equal [| 2 |]
let k2, arr2 = result[2]
k2 |> should equal false
arr2 |> should equal [| 3; 3 |]
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-chunkBy all elements same key as variants`` variant = task {
let ts = Gen.getSeqImmutable variant
let! result = TaskSeq.chunkBy (fun _ -> 0) ts |> TaskSeq.toArrayAsync
result |> should haveLength 1
let _, arr = result[0]
arr |> should haveLength 10
}
93 changes: 93 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.FirstLastDefault.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
module TaskSeq.Tests.FirstLastDefault

open Xunit
open FsUnit.Xunit

open FSharp.Control

//
// TaskSeq.firstOrDefault
// TaskSeq.lastOrDefault
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.firstOrDefault 0 null
assertNullArg <| fun () -> TaskSeq.lastOrDefault 0 null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-firstOrDefault returns default for empty`` variant = task {
let! result = Gen.getEmptyVariant variant |> TaskSeq.firstOrDefault 42
result |> should equal 42
}

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-lastOrDefault returns default for empty`` variant = task {
let! result = Gen.getEmptyVariant variant |> TaskSeq.lastOrDefault 99
result |> should equal 99
}

[<Fact>]
let ``TaskSeq-firstOrDefault returns default with reference type`` () = task {
let! result = TaskSeq.empty<string> |> TaskSeq.firstOrDefault "hello"
result |> should equal "hello"
}

[<Fact>]
let ``TaskSeq-lastOrDefault returns default with reference type`` () = task {
let! result = TaskSeq.empty<string> |> TaskSeq.lastOrDefault "world"
result |> should equal "world"
}


module Immutable =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-firstOrDefault returns first element`` variant = task {
let ts = Gen.getSeqImmutable variant
let! result = TaskSeq.firstOrDefault 0 ts
result |> should equal 1
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-lastOrDefault returns last element`` variant = task {
let ts = Gen.getSeqImmutable variant
let! result = TaskSeq.lastOrDefault 0 ts
result |> should equal 10
}

[<Fact>]
let ``TaskSeq-firstOrDefault does not use default when non-empty`` () = task {
let! result =
taskSeq {
yield 5
yield 6
}
|> TaskSeq.firstOrDefault -1

result |> should equal 5
}

[<Fact>]
let ``TaskSeq-lastOrDefault does not use default when non-empty`` () = task {
let! result =
taskSeq {
yield 5
yield 6
}
|> TaskSeq.lastOrDefault -1

result |> should equal 6
}

[<Fact>]
let ``TaskSeq-firstOrDefault with singleton`` () = task {
let! result = TaskSeq.singleton 42 |> TaskSeq.firstOrDefault 0
result |> should equal 42
}

[<Fact>]
let ``TaskSeq-lastOrDefault with singleton`` () = task {
let! result = TaskSeq.singleton 42 |> TaskSeq.lastOrDefault 0
result |> should equal 42
}
Loading
Loading