From e21c848230fc0f813d373a37c290615998f049ed Mon Sep 17 00:00:00 2001 From: Christer van der Meeren Date: Fri, 6 Sep 2024 14:27:57 +0200 Subject: [PATCH] Add BeAscending/BeDescending string assertions --- RELEASE_NOTES.md | 5 + src/Faqt.Tests/SeqAssertions.fs | 482 ++++++++++++++++++++++++++++++++ src/Faqt/SeqAssertions.fs | 134 +++++++++ 3 files changed, 621 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a207e23..b210e65 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,11 @@ Release notes ============== +### Unreleased + +* Added `seq` assertions `BeAscending` and `BeDescending` with `StringComparison`, `CultureInfo` and + `CompareOptions` parameters + ### 4.0.1 (2024-08-24) * Reduced the required FSharp.Core version from 7.0.400 to 5.0.2 diff --git a/src/Faqt.Tests/SeqAssertions.fs b/src/Faqt.Tests/SeqAssertions.fs index a20cb9a..c0450df 100644 --- a/src/Faqt.Tests/SeqAssertions.fs +++ b/src/Faqt.Tests/SeqAssertions.fs @@ -1,6 +1,7 @@ module SeqAssertions open System +open System.Globalization open Faqt open Xunit @@ -2252,6 +2253,246 @@ Subject value: [1, 2, 6, 3, 1, 3] """ +module ``BeAscending (StringComparison)`` = + + + [] + let ``Can be chained with And`` () = + [] + .Should() + .BeAscending(StringComparison.Ordinal) + .Id>() + .And.Be([]) + + + let passData = [ + [| box List.Empty; StringComparison.Ordinal |] + [| [ "a" ]; StringComparison.Ordinal |] + [| [ "a"; "a" ]; StringComparison.Ordinal |] + [| [ "a"; "b" ]; StringComparison.Ordinal |] + [| [ "A"; "b" ]; StringComparison.OrdinalIgnoreCase |] + [| [ "a"; "B" ]; StringComparison.OrdinalIgnoreCase |] + ] + + + [] + [] + let ``Passes if non-strictly ascending using the specified comparison`` + (subject: seq) + (comparison: StringComparison) + = + subject.Should().BeAscending(comparison) + + + [] + let ``Fails with expected message if null`` () = + fun () -> + let x: seq = null + x.Should().BeAscending(StringComparison.Ordinal) + |> assertExnMsg + """ +Subject: x +Should: BeAscending +Using StringComparison: Ordinal +But was: null +""" + + + [] + let ``Fails with expected message with because if null`` () = + fun () -> + let x: seq = null + x.Should().BeAscending(StringComparison.Ordinal, "Some reason") + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeAscending +Using StringComparison: Ordinal +But was: null +""" + + + [] + let ``Fails with expected message if not in ascending order`` () = + fun () -> + let x = [ "a"; "b"; "f"; "c"; "a"; "c" ] + x.Should().BeAscending(StringComparison.OrdinalIgnoreCase) + |> assertExnMsg + """ +Subject: x +Should: BeAscending +Using StringComparison: OrdinalIgnoreCase +But found: +- Index: 2 + Item: f +- Index: 3 + Item: c +Subject value: [a, b, f, c, a, c] +""" + + + [] + let ``Fails with expected message with because if not in ascending order`` () = + fun () -> + let x = [ "a"; "b"; "f"; "c"; "a"; "c" ] + x.Should().BeAscending(StringComparison.OrdinalIgnoreCase, "Some reason") + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeAscending +Using StringComparison: OrdinalIgnoreCase +But found: +- Index: 2 + Item: f +- Index: 3 + Item: c +Subject value: [a, b, f, c, a, c] +""" + +module ``BeAscending (Culture CompareOptions)`` = + + + [] + let ``Can be chained with And`` () = + [] + .Should() + .BeAscending(CultureInfo.InvariantCulture, CompareOptions.None) + .Id>() + .And.Be([]) + + + let passData = [ + [| box List.Empty; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "a" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "a"; "a" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "a"; "b" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "A"; "b" ]; CultureInfo.InvariantCulture; CompareOptions.IgnoreCase |] + [| [ "a"; "B" ]; CultureInfo.InvariantCulture; CompareOptions.IgnoreCase |] + [| [ "æ"; "ø"; "å" ]; CultureInfo("nb-NO"); CompareOptions.None |] + ] + + + [] + [] + let ``Passes if non-strictly ascending using the specified comparison`` + (subject: seq) + (culture: CultureInfo) + (compareOptions: CompareOptions) + = + subject.Should().BeAscending(culture, compareOptions) + + + let failData = [ + [| box [ "a"; "B" ]; CultureInfo.InvariantCulture; CompareOptions.Ordinal |] + [| box [ "b"; "a" ]; CultureInfo.InvariantCulture; CompareOptions.IgnoreCase |] + [| [ "æ"; "ø"; "å" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + ] + + + [] + [] + let ``Fails if not non-strictly ascending using the specified comparison`` + (subject: seq) + (culture: CultureInfo) + (compareOptions: CompareOptions) + = + assertFails (fun () -> subject.Should().BeAscending(culture, compareOptions)) + + + [] + let ``Fails with expected message if null`` () = + fun () -> + let x: seq = null + + x + .Should() + .BeAscending(CultureInfo("nb-NO"), CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols) + |> assertExnMsg + """ +Subject: x +Should: BeAscending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But was: null +""" + + + [] + let ``Fails with expected message with because if null`` () = + fun () -> + let x: seq = null + + x + .Should() + .BeAscending( + CultureInfo("nb-NO"), + CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols, + "Some reason" + ) + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeAscending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But was: null +""" + + + [] + let ``Fails with expected message if not in ascending order`` () = + fun () -> + let x = [ "a"; "b"; "f"; "c"; "a"; "c" ] + + x + .Should() + .BeAscending(CultureInfo("nb-NO"), CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols) + |> assertExnMsg + """ +Subject: x +Should: BeAscending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But found: +- Index: 2 + Item: f +- Index: 3 + Item: c +Subject value: [a, b, f, c, a, c] +""" + + + [] + let ``Fails with expected message with because if not in ascending order`` () = + fun () -> + let x = [ "a"; "b"; "f"; "c"; "a"; "c" ] + + x + .Should() + .BeAscending( + CultureInfo("nb-NO"), + CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols, + "Some reason" + ) + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeAscending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But found: +- Index: 2 + Item: f +- Index: 3 + Item: c +Subject value: [a, b, f, c, a, c] +""" + + module BeAscendingBy = @@ -2430,6 +2671,247 @@ Subject value: [3, 1, 3, 6, 2, 1] """ +module ``BeDescending (StringComparison)`` = + + + [] + let ``Can be chained with And`` () = + [] + .Should() + .BeDescending(StringComparison.Ordinal) + .Id>() + .And.Be([]) + + + let passData = [ + [| box List.Empty; StringComparison.Ordinal |] + [| [ "a" ]; StringComparison.Ordinal |] + [| [ "a"; "a" ]; StringComparison.Ordinal |] + [| [ "b"; "a" ]; StringComparison.Ordinal |] + [| [ "b"; "A" ]; StringComparison.OrdinalIgnoreCase |] + [| [ "B"; "a" ]; StringComparison.OrdinalIgnoreCase |] + ] + + + [] + [] + let ``Passes if non-strictly descending using the specified comparison`` + (subject: seq) + (comparison: StringComparison) + = + subject.Should().BeDescending(comparison) + + + [] + let ``Fails with expected message if null`` () = + fun () -> + let x: seq = null + x.Should().BeDescending(StringComparison.Ordinal) + |> assertExnMsg + """ +Subject: x +Should: BeDescending +Using StringComparison: Ordinal +But was: null +""" + + + [] + let ``Fails with expected message with because if null`` () = + fun () -> + let x: seq = null + x.Should().BeDescending(StringComparison.Ordinal, "Some reason") + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeDescending +Using StringComparison: Ordinal +But was: null +""" + + + [] + let ``Fails with expected message if not in descending order`` () = + fun () -> + let x = [ "c"; "a"; "c"; "f"; "b"; "a" ] + x.Should().BeDescending(StringComparison.OrdinalIgnoreCase) + |> assertExnMsg + """ +Subject: x +Should: BeDescending +Using StringComparison: OrdinalIgnoreCase +But found: +- Index: 1 + Item: a +- Index: 2 + Item: c +Subject value: [c, a, c, f, b, a] +""" + + + [] + let ``Fails with expected message with because if not in descending order`` () = + fun () -> + let x = [ "c"; "a"; "c"; "f"; "b"; "a" ] + x.Should().BeDescending(StringComparison.OrdinalIgnoreCase, "Some reason") + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeDescending +Using StringComparison: OrdinalIgnoreCase +But found: +- Index: 1 + Item: a +- Index: 2 + Item: c +Subject value: [c, a, c, f, b, a] +""" + + +module ``BeDescending (Culture CompareOptions)`` = + + + [] + let ``Can be chained with And`` () = + [] + .Should() + .BeDescending(CultureInfo.InvariantCulture, CompareOptions.None) + .Id>() + .And.Be([]) + + + let passData = [ + [| box List.Empty; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "a" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "a"; "a" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "a"; "b" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + [| [ "A"; "b" ]; CultureInfo.InvariantCulture; CompareOptions.IgnoreCase |] + [| [ "a"; "B" ]; CultureInfo.InvariantCulture; CompareOptions.IgnoreCase |] + [| [ "æ"; "ø"; "å" ]; CultureInfo("nb-NO"); CompareOptions.None |] + ] + + + [] + [] + let ``Passes if non-strictly descending using the specified comparison`` + (subject: seq) + (culture: CultureInfo) + (compareOptions: CompareOptions) + = + subject.Should().BeDescending(culture, compareOptions) + + + let failData = [ + [| box [ "a"; "B" ]; CultureInfo.InvariantCulture; CompareOptions.Ordinal |] + [| box [ "b"; "a" ]; CultureInfo.InvariantCulture; CompareOptions.IgnoreCase |] + [| [ "æ"; "ø"; "å" ]; CultureInfo.InvariantCulture; CompareOptions.None |] + ] + + + [] + [] + let ``Fails if not non-strictly descending using the specified comparison`` + (subject: seq) + (culture: CultureInfo) + (compareOptions: CompareOptions) + = + assertFails (fun () -> subject.Should().BeDescending(culture, compareOptions)) + + + [] + let ``Fails with expected message if null`` () = + fun () -> + let x: seq = null + + x + .Should() + .BeDescending(CultureInfo("nb-NO"), CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols) + |> assertExnMsg + """ +Subject: x +Should: BeDescending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But was: null +""" + + + [] + let ``Fails with expected message with because if null`` () = + fun () -> + let x: seq = null + + x + .Should() + .BeDescending( + CultureInfo("nb-NO"), + CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols, + "Some reason" + ) + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeDescending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But was: null +""" + + + [] + let ``Fails with expected message if not in descending order`` () = + fun () -> + let x = [ "c"; "a"; "c"; "f"; "b"; "a" ] + + x + .Should() + .BeDescending(CultureInfo("nb-NO"), CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols) + |> assertExnMsg + """ +Subject: x +Should: BeDescending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But found: +- Index: 1 + Item: a +- Index: 2 + Item: c +Subject value: [c, a, c, f, b, a] +""" + + + [] + let ``Fails with expected message with because if not in descending order`` () = + fun () -> + let x = [ "c"; "a"; "c"; "f"; "b"; "a" ] + + x + .Should() + .BeDescending( + CultureInfo("nb-NO"), + CompareOptions.IgnoreCase ||| CompareOptions.IgnoreSymbols, + "Some reason" + ) + |> assertExnMsg + """ +Subject: x +Because: Some reason +Should: BeDescending +In culture: nb-NO +With CompareOptions: IgnoreCase, IgnoreSymbols +But found: +- Index: 1 + Item: a +- Index: 2 + Item: c +Subject value: [c, a, c, f, b, a] +""" + + module BeDescendingBy = diff --git a/src/Faqt/SeqAssertions.fs b/src/Faqt/SeqAssertions.fs index 89d399e..4edeb7c 100644 --- a/src/Faqt/SeqAssertions.fs +++ b/src/Faqt/SeqAssertions.fs @@ -1,6 +1,8 @@ namespace Faqt +open System open System.Collections.Generic +open System.Globalization open System.Runtime.CompilerServices open Faqt.AssertionHelpers open Faqt.Formatting @@ -679,6 +681,72 @@ type SeqAssertions = And(t) + /// Asserts that the subject is in ascending order using the specified comparison type. + [] + static member BeAscending(t: Testable<#seq>, comparisonType: StringComparison, ?because) : And<_> = + use _ = t.Assert() + + if isNull (box t.Subject) then + t + .With("Using StringComparison", comparisonType) + .With( + comparisonType = StringComparison.CurrentCulture + || comparisonType = StringComparison.CurrentCultureIgnoreCase, + "CurrentCulture", + CultureInfo.CurrentCulture + ) + .With("But was", t.Subject) + .Fail(because) + + for i, (a, b) in t.Subject |> Seq.pairwise |> Seq.indexed do + + if String.Compare(a, b, comparisonType) > 0 then + t + .With("Using StringComparison", comparisonType) + .With( + comparisonType = StringComparison.CurrentCulture + || comparisonType = StringComparison.CurrentCultureIgnoreCase, + "CurrentCulture", + CultureInfo.CurrentCulture + ) + .With("But found", [ {| Index = i; Item = TryFormat a |}; {| Index = i + 1; Item = TryFormat b |} ]) + .With("Subject value", t.Subject) + .Fail(because) + + And(t) + + + /// Asserts that the subject is in ascending order using the specified culture and compare options. + [] + static member BeAscending + ( + t: Testable<#seq>, + culture: CultureInfo, + compareOptions: CompareOptions, + ?because + ) : And<_> = + use _ = t.Assert() + + if isNull (box t.Subject) then + t + .With("In culture", culture) + .With("With CompareOptions", compareOptions) + .With("But was", t.Subject) + .Fail(because) + + for i, (a, b) in t.Subject |> Seq.pairwise |> Seq.indexed do + + if String.Compare(a, b, culture, compareOptions) > 0 then + t + .With("In culture", culture) + .With("With CompareOptions", compareOptions) + .With("But found", [ {| Index = i; Item = TryFormat a |}; {| Index = i + 1; Item = TryFormat b |} ]) + .With("Subject value", t.Subject) + .Fail(because) + + And(t) + + /// Asserts that the subject is in ascending order by the specified projection. [] static member BeAscendingBy(t: Testable<#seq<'a>>, projection: 'a -> 'b, ?because) : And<_> = @@ -733,6 +801,72 @@ type SeqAssertions = And(t) + /// Asserts that the subject is in descending order using the specified comparison type. + [] + static member BeDescending(t: Testable<#seq>, comparisonType: StringComparison, ?because) : And<_> = + use _ = t.Assert() + + if isNull (box t.Subject) then + t + .With("Using StringComparison", comparisonType) + .With( + comparisonType = StringComparison.CurrentCulture + || comparisonType = StringComparison.CurrentCultureIgnoreCase, + "CurrentCulture", + CultureInfo.CurrentCulture + ) + .With("But was", t.Subject) + .Fail(because) + + for i, (a, b) in t.Subject |> Seq.pairwise |> Seq.indexed do + + if String.Compare(a, b, comparisonType) > 0 then + t + .With("Using StringComparison", comparisonType) + .With( + comparisonType = StringComparison.CurrentCulture + || comparisonType = StringComparison.CurrentCultureIgnoreCase, + "CurrentCulture", + CultureInfo.CurrentCulture + ) + .With("But found", [ {| Index = i; Item = TryFormat a |}; {| Index = i + 1; Item = TryFormat b |} ]) + .With("Subject value", t.Subject) + .Fail(because) + + And(t) + + + /// Asserts that the subject is in descending order using the specified culture and compare options. + [] + static member BeDescending + ( + t: Testable<#seq>, + culture: CultureInfo, + compareOptions: CompareOptions, + ?because + ) : And<_> = + use _ = t.Assert() + + if isNull (box t.Subject) then + t + .With("In culture", culture) + .With("With CompareOptions", compareOptions) + .With("But was", t.Subject) + .Fail(because) + + for i, (a, b) in t.Subject |> Seq.pairwise |> Seq.indexed do + + if String.Compare(a, b, culture, compareOptions) < 0 then + t + .With("In culture", culture) + .With("With CompareOptions", compareOptions) + .With("But found", [ {| Index = i; Item = TryFormat a |}; {| Index = i + 1; Item = TryFormat b |} ]) + .With("Subject value", t.Subject) + .Fail(because) + + And(t) + + /// Asserts that the subject is in descending order by the specified projection. [] static member BeDescendingBy(t: Testable<#seq<'a>>, projection: 'a -> 'b, ?because) : And<_> =