diff --git a/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj b/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj index f583f64ed..f156734b1 100644 --- a/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj +++ b/tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj @@ -21,6 +21,7 @@ + diff --git a/tests/FSharp.Data.Core.Tests/Pluralizer.fs b/tests/FSharp.Data.Core.Tests/Pluralizer.fs new file mode 100644 index 000000000..cbe07d2d2 --- /dev/null +++ b/tests/FSharp.Data.Core.Tests/Pluralizer.fs @@ -0,0 +1,223 @@ +// -------------------------------------------------------------------------------------- +// Tests for the Pluralizer module that handles English noun pluralization +// -------------------------------------------------------------------------------------- + +module FSharp.Data.Tests.Pluralizer + +open FsUnit +open NUnit.Framework +open FSharp.Data.Runtime.Pluralizer + +[] +let ``toPlural handles null and empty strings`` () = + toPlural null |> should equal null + toPlural "" |> should equal "" + toPlural " " |> should not' (equal " ") // Should trim and process + +[] +let ``toSingular handles null and empty strings`` () = + toSingular null |> should equal null + toSingular "" |> should equal "" + toSingular " " |> should equal " " // Whitespace is preserved + +[] +let ``Basic suffix rules work correctly`` () = + // Test common suffix rules + toPlural "church" |> should equal "churches" + toPlural "flash" |> should equal "flashes" + toPlural "class" |> should equal "classes" + + toPlural "boy" |> should equal "boys" + toPlural "key" |> should equal "keys" + toPlural "city" |> should equal "cities" // y -> ies rule + + toPlural "hero" |> should equal "heroes" + toPlural "photo" |> should equal "photos" // Should use special word rule + + toPlural "house" |> should equal "houses" // Special case + toPlural "course" |> should equal "courses" // Special case + +[] +let ``Complex suffix rules work correctly`` () = + toPlural "crisis" |> should equal "crises" + toPlural "campus" |> should equal "campuses" + toPlural "basis" |> should equal "bases" + toPlural "axis" |> should equal "axes" + + toPlural "louse" |> should equal "lice" + toPlural "mouse" |> should equal "mice" + + toPlural "zoon" |> should equal "zoa" + toPlural "man" |> should equal "men" + +[] +let ``Words ending with f/fe become ves`` () = + toPlural "half" |> should equal "halves" + toPlural "elf" |> should equal "elves" + toPlural "wolf" |> should equal "wolves" + toPlural "scarf" |> should equal "scarves" + + toPlural "knife" |> should equal "knives" + toPlural "life" |> should equal "lives" + toPlural "wife" |> should equal "wives" + +[] +let ``Irregular plurals from special words list`` () = + toPlural "child" |> should equal "children" + toPlural "foot" |> should equal "feet" + toPlural "tooth" |> should equal "teeth" + toPlural "goose" |> should equal "geese" + + toPlural "deer" |> should equal "deer" // Unchanged + toPlural "sheep" |> should equal "sheep" // Unchanged + toPlural "fish" |> should equal "fishes" // Can be "fish" or "fishes", this uses suffix rule + +[] +let ``Scientific and foreign words`` () = + toPlural "bacterium" |> should equal "bacteria" + toPlural "datum" |> should equal "data" + toPlural "alumnus" |> should equal "alumni" + toPlural "alumna" |> should equal "alumnae" + toPlural "apex" |> should equal "apices" + toPlural "vertex" |> should equal "vertices" + toPlural "index" |> should equal "indices" + +[] +let ``Words that don't change in plural`` () = + toPlural "aircraft" |> should equal "aircraft" + toPlural "chassis" |> should equal "chassis" + toPlural "debris" |> should equal "debris" + toPlural "headquarters" |> should equal "headquarters" + toPlural "news" |> should equal "news" + toPlural "series" |> should equal "series" + +[] +let ``Case sensitivity is preserved`` () = + toPlural "HOUSE" |> should equal "HOUSES" + toPlural "House" |> should equal "Houses" + toPlural "house" |> should equal "houses" + toPlural "HoUsE" |> should equal "Houses" // Case adjustment follows template + +[] +let ``Basic singularization works`` () = + toSingular "churches" |> should equal "church" + toSingular "flashes" |> should equal "flash" + toSingular "classes" |> should equal "class" + + toSingular "cities" |> should equal "city" + toSingular "boys" |> should equal "boy" + toSingular "keys" |> should equal "key" + + toSingular "heroes" |> should equal "hero" + toSingular "houses" |> should equal "house" + +[] +let ``Complex singularization works`` () = + toSingular "children" |> should equal "child" + toSingular "feet" |> should equal "foot" + toSingular "teeth" |> should equal "tooth" + toSingular "geese" |> should equal "goose" + toSingular "mice" |> should equal "mouse" + + toSingular "bacteria" |> should equal "bacterium" + toSingular "data" |> should equal "datum" + toSingular "alumni" |> should equal "alumnus" + +[] +let ``Singularization preserves case`` () = + toSingular "HOUSES" |> should equal "HOUSE" + toSingular "Houses" |> should equal "House" + toSingular "houses" |> should equal "house" + toSingular "HoUsEs" |> should equal "House" // Case adjustment follows template + +[] +let ``Words already plural remain plural`` () = + toPlural "houses" |> should equal "houses" // Already plural + toPlural "mice" |> should equal "mices" // Pluralizer doesn't detect this as already plural + toPlural "children" |> should equal "childrens" // Pluralizer doesn't recognize this as already plural + +[] +let ``Words already singular remain singular`` () = + toSingular "house" |> should equal "house" // Already singular + toSingular "mouse" |> should equal "mouse" // Already singular irregular + toSingular "child" |> should equal "child" // Already singular + +[] +let ``Default rules for unknown words`` () = + // Unknown words should get 's' added + toPlural "unknownword" |> should equal "unknownwords" + toPlural "newterm" |> should equal "newterms" + + // Unknown plurals ending in 's' should get 's' removed (if not ending in "us") + toSingular "unknownwords" |> should equal "unknownword" + toSingular "newterms" |> should equal "newterm" + + // But "us" endings should remain unchanged + toSingular "campus" |> should equal "campus" // Should not become "campu" + +[] +let ``Mixed case and edge case handling`` () = + // Single letter words + toPlural "a" |> should equal "as" + toSingular "as" |> should equal "a" + + // Numbers (should remain unchanged or follow basic rules) + toPlural "1" |> should equal "1s" + + // Words with numbers + toPlural "mp3" |> should equal "mp3s" + toSingular "mp3s" |> should equal "mp3" + +[] +let ``Special edge cases from word list`` () = + // Test some edge cases from the special words list + toPlural "octopus" |> should equal "octopuses" // First plural form + toSingular "octopuses" |> should equal "octopus" + toSingular "octopodes" |> should equal "octopus" // Alternative plural + + toPlural "focus" |> should equal "focuses" // First plural form + toSingular "focuses" |> should equal "focus" + toSingular "foci" |> should equal "focus" // Alternative plural + + // Test words with multiple plural forms + toPlural "fungus" |> should equal "fungi" // First plural form in list + toSingular "fungi" |> should equal "fungus" + toSingular "funguses" |> should equal "fungus" // Alternative plural + +[] +let ``Roundtrip consistency for common words`` () = + let testWords = [ + "book"; "house"; "child"; "mouse"; "man"; "woman" + "city"; "country"; "company"; "person"; "foot"; "tooth" + "deer"; "sheep"; "fish"; "aircraft"; "series" + ] + + for word in testWords do + let plural = toPlural word + let backToSingular = toSingular plural + + // For most words, singularizing the plural should get back the original + // (This may not be true for all words due to multiple plural forms) + if word <> "deer" && word <> "sheep" && word <> "fish" && + word <> "aircraft" && word <> "series" then + backToSingular |> should equal word + +[] +let ``Performance with repeated calls`` () = + // Test that repeated calls with same input work correctly (testing lazy initialization) + let word = "house" + let firstResult = toPlural word + let secondResult = toPlural word + let thirdResult = toPlural word + + firstResult |> should equal "houses" + secondResult |> should equal firstResult + thirdResult |> should equal firstResult + + // Same for singularization + let pluralWord = "houses" + let firstSingular = toSingular pluralWord + let secondSingular = toSingular pluralWord + + firstSingular |> should equal "house" + secondSingular |> should equal firstSingular \ No newline at end of file