From 930913379b6eb1ed0a0954e7216f453b9b47c8db Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Mar 2026 12:57:24 +0000 Subject: [PATCH 1/3] test: add coverage for untested List and Array module functions Add 33 new tests covering functions in List and Array that had no test coverage: List: singleton, cons, findExactlyOne, skip, take, skipWhile, skipUntil, takeWhile, takeUntil, groupNeighboursBy, mapIf, catOptions, choice1s, choice2s, partitionChoices, equalsWith Array: nth, setAt, findExactlyOne, centralMovingAverageOfOption, catOptions, choice1s, choice2s, partitionChoices, equalsWith Total test count increases from ~710 to 743. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/FSharpx.Collections.Tests/ArrayTests.fs | 63 ++++++++++++++ .../ListExtensionsTest.fs | 85 +++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/tests/FSharpx.Collections.Tests/ArrayTests.fs b/tests/FSharpx.Collections.Tests/ArrayTests.fs index 18cb9f56..c0a39c57 100644 --- a/tests/FSharpx.Collections.Tests/ArrayTests.fs +++ b/tests/FSharpx.Collections.Tests/ArrayTests.fs @@ -51,4 +51,67 @@ module ArrayTests = Expect.equal "expect arrays equal" expected <| Array.centralMovingAverage 3 data + } + + test "nth returns element at index" { Expect.equal "nth" 3 (Array.nth 2 [| 1; 2; 3; 4; 5 |]) } + + test "setAt mutates element at index and returns array" { + let a = [| 1; 2; 3 |] + let result = Array.setAt 1 99 a + Expect.equal "setAt value" 99 result.[1] + Expect.isTrue "setAt same ref" (obj.ReferenceEquals(a, result)) + } + + test "findExactlyOne returns the sole matching element" { + Expect.equal "findExactlyOne" 3 (Array.findExactlyOne ((=) 3) [| 1; 2; 3; 4; 5 |]) + } + + test "findExactlyOne throws when no element matches" { + Expect.throws "findExactlyOne no match" + <| fun () -> Array.findExactlyOne ((=) 99) [| 1; 2; 3 |] |> ignore + } + + test "centralMovingAverageOfOption handles None entries" { + let a = [| Some 1.0; None; Some 3.0 |] + let result = Array.centralMovingAverageOfOption 1 a + Expect.equal "centralMovingAverageOfOption" [| None; None; None |] result + } + + test "centralMovingAverageOfOption all Some" { + let a = [| Some 1.0; Some 2.0; Some 3.0 |] + let result = Array.centralMovingAverageOfOption 1 a + Expect.equal "centralMovingAverageOfOption all Some" [| Some 1.5; Some 2.0; Some 2.5 |] result + } + + test "catOptions extracts Some values from array of options" { + Expect.equal "catOptions" [| 1; 3 |] (Array.catOptions [| Some 1; None; Some 3; None |]) + } + + test "choice1s extracts Choice1Of2 values" { + let xs = [| Choice1Of2 1; Choice2Of2 "a"; Choice1Of2 2; Choice2Of2 "b" |] + Expect.equal "choice1s" [| 1; 2 |] (Array.choice1s xs) + } + + test "choice2s extracts Choice2Of2 values" { + let xs = [| Choice1Of2 1; Choice2Of2 "a"; Choice1Of2 2; Choice2Of2 "b" |] + Expect.equal "choice2s" [| "a"; "b" |] (Array.choice2s xs) + } + + test "partitionChoices separates Choice1Of2 and Choice2Of2" { + let xs = [| Choice1Of2 1; Choice2Of2 "a"; Choice1Of2 2; Choice2Of2 "b" |] + let c1s, c2s = Array.partitionChoices xs + Expect.equal "partitionChoices c1s" [| 1; 2 |] c1s + Expect.equal "partitionChoices c2s" [| "a"; "b" |] c2s + } + + test "equalsWith returns true for element-wise equal arrays" { + Expect.isTrue "equalsWith true" (Array.equalsWith (=) [| 1; 2; 3 |] [| 1; 2; 3 |]) + } + + test "equalsWith returns false for unequal arrays" { + Expect.isFalse "equalsWith false" (Array.equalsWith (=) [| 1; 2; 3 |] [| 1; 2; 4 |]) + } + + test "equalsWith returns false for arrays of different length" { + Expect.isFalse "equalsWith length" (Array.equalsWith (=) [| 1; 2 |] [| 1; 2; 3 |]) } ] diff --git a/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs b/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs index 5a2244c4..3af62deb 100644 --- a/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs +++ b/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs @@ -80,6 +80,91 @@ module ListExtensionsTests = let a = [ [ 1; 2; 3 ]; [ 4; 5; 6 ] ] let expected = [ [ 1; 4 ]; [ 2; 5 ]; [ 3; 6 ] ] Expect.equal "transpose" expected (a |> List.transpose) + } + + test "singleton creates a one-element list" { Expect.equal "singleton" [ 42 ] (List.singleton 42) } + + test "cons prepends an element" { Expect.equal "cons" [ 1; 2; 3 ] (List.cons 1 [ 2; 3 ]) } + + test "findExactlyOne returns the sole matching element" { + Expect.equal "findExactlyOne" 3 (List.findExactlyOne ((=) 3) [ 1; 2; 3; 4; 5 ]) + } + + test "findExactlyOne throws when no element matches" { + Expect.throws "findExactlyOne no match" (fun () -> List.findExactlyOne ((=) 99) [ 1; 2; 3 ] |> ignore) + } + + test "findExactlyOne throws when multiple elements match" { + Expect.throws "findExactlyOne multiple" (fun () -> List.findExactlyOne ((=) 1) [ 1; 1; 2 ] |> ignore) + } + + test "skip removes first n elements" { Expect.equal "skip" [ 3; 4; 5 ] (List.skip 2 [ 1; 2; 3; 4; 5 ]) } + + test "skip 0 returns whole list" { Expect.equal "skip 0" [ 1; 2; 3 ] (List.skip 0 [ 1; 2; 3 ]) } + + test "take returns first n elements" { Expect.equal "take" [ 1; 2 ] (List.take 2 [ 1; 2; 3; 4; 5 ]) } + + test "take 0 returns empty list" { Expect.equal "take 0" [] (List.take 0 [ 1; 2; 3 ]) } + + test "skipWhile skips leading elements satisfying predicate" { + Expect.equal "skipWhile" [ 3; 4; 5 ] (List.skipWhile (fun x -> x < 3) [ 1; 2; 3; 4; 5 ]) + } + + test "skipUntil skips until predicate is satisfied" { + Expect.equal "skipUntil" [ 3; 4; 5 ] (List.skipUntil (fun x -> x = 3) [ 1; 2; 3; 4; 5 ]) + } + + test "takeWhile takes leading elements satisfying predicate" { + Expect.equal "takeWhile" [ 1; 2 ] (List.takeWhile (fun x -> x < 3) [ 1; 2; 3; 4; 5 ]) + } + + test "takeUntil takes until predicate is satisfied" { + Expect.equal "takeUntil" [ 1; 2 ] (List.takeUntil (fun x -> x = 3) [ 1; 2; 3; 4; 5 ]) + } + + test "groupNeighboursBy groups consecutive equal keys" { + let result = List.groupNeighboursBy id [ 1; 1; 2; 2; 1 ] + Expect.equal "groupNeighboursBy" [ (1, [ 1; 1 ]); (2, [ 2; 2 ]); (1, [ 1 ]) ] result + } + + test "groupNeighboursBy on empty list" { Expect.equal "groupNeighboursBy empty" [] (List.groupNeighboursBy id []) } + + test "mapIf maps elements matching predicate, leaves others unchanged" { + let result = List.mapIf (fun x -> x % 2 = 0) ((*) 10) [ 1; 2; 3; 4; 5 ] + Expect.equal "mapIf" [ 1; 20; 3; 40; 5 ] result + } + + test "catOptions extracts Some values from list of options" { + Expect.equal "catOptions" [ 1; 3 ] (List.catOptions [ Some 1; None; Some 3; None ]) + } + + test "catOptions on all-None list" { Expect.equal "catOptions all-None" [] (List.catOptions [ None; None ]) } + + test "choice1s extracts Choice1Of2 values" { + let xs = [ Choice1Of2 1; Choice2Of2 "a"; Choice1Of2 2; Choice2Of2 "b" ] + Expect.equal "choice1s" [ 1; 2 ] (List.choice1s xs) + } + + test "choice2s extracts Choice2Of2 values" { + let xs = [ Choice1Of2 1; Choice2Of2 "a"; Choice1Of2 2; Choice2Of2 "b" ] + Expect.equal "choice2s" [ "a"; "b" ] (List.choice2s xs) + } + + test "partitionChoices separates Choice1Of2 and Choice2Of2" { + let xs = [ Choice1Of2 1; Choice2Of2 "a"; Choice1Of2 2; Choice2Of2 "b" ] + let c1s, c2s = List.partitionChoices xs + Expect.equal "partitionChoices c1s" [ 1; 2 ] c1s + Expect.equal "partitionChoices c2s" [ "a"; "b" ] c2s + } + + test "equalsWith returns true for element-wise equal lists" { + Expect.isTrue "equalsWith true" (List.equalsWith (=) [ 1; 2; 3 ] [ 1; 2; 3 ]) + } + + test "equalsWith returns false for unequal lists" { Expect.isFalse "equalsWith false" (List.equalsWith (=) [ 1; 2; 3 ] [ 1; 2; 4 ]) } + + test "equalsWith returns false for lists of different length" { + Expect.isFalse "equalsWith length" (List.equalsWith (=) [ 1; 2 ] [ 1; 2; 3 ]) } ] [] From d4d2086ae1b97ad266211b5158ea856d5b1b2466 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Mar 2026 13:04:06 +0000 Subject: [PATCH 2/3] ci: trigger checks From ba7c7210cb91d9a6379dd0b460bea6d0dcf80b50 Mon Sep 17 00:00:00 2001 From: Grzegorz Dziadkiewicz Date: Sun, 15 Mar 2026 16:23:44 +0100 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/FSharpx.Collections.Tests/ArrayTests.fs | 2 +- tests/FSharpx.Collections.Tests/ListExtensionsTest.fs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/FSharpx.Collections.Tests/ArrayTests.fs b/tests/FSharpx.Collections.Tests/ArrayTests.fs index c0a39c57..5ea53370 100644 --- a/tests/FSharpx.Collections.Tests/ArrayTests.fs +++ b/tests/FSharpx.Collections.Tests/ArrayTests.fs @@ -67,7 +67,7 @@ module ArrayTests = } test "findExactlyOne throws when no element matches" { - Expect.throws "findExactlyOne no match" + Expect.throwsT "findExactlyOne no match" <| fun () -> Array.findExactlyOne ((=) 99) [| 1; 2; 3 |] |> ignore } diff --git a/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs b/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs index 3af62deb..f5c50783 100644 --- a/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs +++ b/tests/FSharpx.Collections.Tests/ListExtensionsTest.fs @@ -91,11 +91,11 @@ module ListExtensionsTests = } test "findExactlyOne throws when no element matches" { - Expect.throws "findExactlyOne no match" (fun () -> List.findExactlyOne ((=) 99) [ 1; 2; 3 ] |> ignore) + Expect.throwsT "findExactlyOne no match" (fun () -> List.findExactlyOne ((=) 99) [ 1; 2; 3 ] |> ignore) } test "findExactlyOne throws when multiple elements match" { - Expect.throws "findExactlyOne multiple" (fun () -> List.findExactlyOne ((=) 1) [ 1; 1; 2 ] |> ignore) + Expect.throwsT "findExactlyOne multiple" (fun () -> List.findExactlyOne ((=) 1) [ 1; 1; 2 ] |> ignore) } test "skip removes first n elements" { Expect.equal "skip" [ 3; 4; 5 ] (List.skip 2 [ 1; 2; 3; 4; 5 ]) }