1+ // --------------------------------------------------------------------------------------
2+ // Tests for the Pluralizer module that handles English noun pluralization
3+ // --------------------------------------------------------------------------------------
4+
5+ module FSharp.Data.Tests.Pluralizer
6+
7+ open FsUnit
8+ open NUnit.Framework
9+ open FSharp.Data .Runtime .Pluralizer
10+
11+ [<Test>]
12+ let ``toPlural handles null and empty strings`` () =
13+ toPlural null |> should equal null
14+ toPlural " " |> should equal " "
15+ toPlural " " |> should not' ( equal " " ) // Should trim and process
16+
17+ [<Test>]
18+ let ``toSingular handles null and empty strings`` () =
19+ toSingular null |> should equal null
20+ toSingular " " |> should equal " "
21+ toSingular " " |> should equal " " // Whitespace is preserved
22+
23+ [<Test>]
24+ let ``Basic suffix rules work correctly`` () =
25+ // Test common suffix rules
26+ toPlural " church" |> should equal " churches"
27+ toPlural " flash" |> should equal " flashes"
28+ toPlural " class" |> should equal " classes"
29+
30+ toPlural " boy" |> should equal " boys"
31+ toPlural " key" |> should equal " keys"
32+ toPlural " city" |> should equal " cities" // y -> ies rule
33+
34+ toPlural " hero" |> should equal " heroes"
35+ toPlural " photo" |> should equal " photos" // Should use special word rule
36+
37+ toPlural " house" |> should equal " houses" // Special case
38+ toPlural " course" |> should equal " courses" // Special case
39+
40+ [<Test>]
41+ let ``Complex suffix rules work correctly`` () =
42+ toPlural " crisis" |> should equal " crises"
43+ toPlural " campus" |> should equal " campuses"
44+ toPlural " basis" |> should equal " bases"
45+ toPlural " axis" |> should equal " axes"
46+
47+ toPlural " louse" |> should equal " lice"
48+ toPlural " mouse" |> should equal " mice"
49+
50+ toPlural " zoon" |> should equal " zoa"
51+ toPlural " man" |> should equal " men"
52+
53+ [<Test>]
54+ let ``Words ending with f / fe become ves`` () =
55+ toPlural " half" |> should equal " halves"
56+ toPlural " elf" |> should equal " elves"
57+ toPlural " wolf" |> should equal " wolves"
58+ toPlural " scarf" |> should equal " scarves"
59+
60+ toPlural " knife" |> should equal " knives"
61+ toPlural " life" |> should equal " lives"
62+ toPlural " wife" |> should equal " wives"
63+
64+ [<Test>]
65+ let ``Irregular plurals from special words list`` () =
66+ toPlural " child" |> should equal " children"
67+ toPlural " foot" |> should equal " feet"
68+ toPlural " tooth" |> should equal " teeth"
69+ toPlural " goose" |> should equal " geese"
70+
71+ toPlural " deer" |> should equal " deer" // Unchanged
72+ toPlural " sheep" |> should equal " sheep" // Unchanged
73+ toPlural " fish" |> should equal " fishes" // Can be "fish" or "fishes", this uses suffix rule
74+
75+ [<Test>]
76+ let ``Scientific and foreign words`` () =
77+ toPlural " bacterium" |> should equal " bacteria"
78+ toPlural " datum" |> should equal " data"
79+ toPlural " alumnus" |> should equal " alumni"
80+ toPlural " alumna" |> should equal " alumnae"
81+ toPlural " apex" |> should equal " apices"
82+ toPlural " vertex" |> should equal " vertices"
83+ toPlural " index" |> should equal " indices"
84+
85+ [<Test>]
86+ let ``Words that don 't change in plural`` () =
87+ toPlural " aircraft" |> should equal " aircraft"
88+ toPlural " chassis" |> should equal " chassis"
89+ toPlural " debris" |> should equal " debris"
90+ toPlural " headquarters" |> should equal " headquarters"
91+ toPlural " news" |> should equal " news"
92+ toPlural " series" |> should equal " series"
93+
94+ [<Test>]
95+ let ``Case sensitivity is preserved`` () =
96+ toPlural " HOUSE" |> should equal " HOUSES"
97+ toPlural " House" |> should equal " Houses"
98+ toPlural " house" |> should equal " houses"
99+ toPlural " HoUsE" |> should equal " Houses" // Case adjustment follows template
100+
101+ [<Test>]
102+ let ``Basic singularization works`` () =
103+ toSingular " churches" |> should equal " church"
104+ toSingular " flashes" |> should equal " flash"
105+ toSingular " classes" |> should equal " class"
106+
107+ toSingular " cities" |> should equal " city"
108+ toSingular " boys" |> should equal " boy"
109+ toSingular " keys" |> should equal " key"
110+
111+ toSingular " heroes" |> should equal " hero"
112+ toSingular " houses" |> should equal " house"
113+
114+ [<Test>]
115+ let ``Complex singularization works`` () =
116+ toSingular " children" |> should equal " child"
117+ toSingular " feet" |> should equal " foot"
118+ toSingular " teeth" |> should equal " tooth"
119+ toSingular " geese" |> should equal " goose"
120+ toSingular " mice" |> should equal " mouse"
121+
122+ toSingular " bacteria" |> should equal " bacterium"
123+ toSingular " data" |> should equal " datum"
124+ toSingular " alumni" |> should equal " alumnus"
125+
126+ [<Test>]
127+ let ``Singularization preserves case`` () =
128+ toSingular " HOUSES" |> should equal " HOUSE"
129+ toSingular " Houses" |> should equal " House"
130+ toSingular " houses" |> should equal " house"
131+ toSingular " HoUsEs" |> should equal " House" // Case adjustment follows template
132+
133+ [<Test>]
134+ let ``Words already plural remain plural`` () =
135+ toPlural " houses" |> should equal " houses" // Already plural
136+ toPlural " mice" |> should equal " mices" // Pluralizer doesn't detect this as already plural
137+ toPlural " children" |> should equal " childrens" // Pluralizer doesn't recognize this as already plural
138+
139+ [<Test>]
140+ let ``Words already singular remain singular`` () =
141+ toSingular " house" |> should equal " house" // Already singular
142+ toSingular " mouse" |> should equal " mouse" // Already singular irregular
143+ toSingular " child" |> should equal " child" // Already singular
144+
145+ [<Test>]
146+ let ``Default rules for unknown words`` () =
147+ // Unknown words should get 's' added
148+ toPlural " unknownword" |> should equal " unknownwords"
149+ toPlural " newterm" |> should equal " newterms"
150+
151+ // Unknown plurals ending in 's' should get 's' removed (if not ending in "us")
152+ toSingular " unknownwords" |> should equal " unknownword"
153+ toSingular " newterms" |> should equal " newterm"
154+
155+ // But "us" endings should remain unchanged
156+ toSingular " campus" |> should equal " campus" // Should not become "campu"
157+
158+ [<Test>]
159+ let ``Mixed case and edge case handling`` () =
160+ // Single letter words
161+ toPlural " a" |> should equal " as"
162+ toSingular " as" |> should equal " a"
163+
164+ // Numbers (should remain unchanged or follow basic rules)
165+ toPlural " 1" |> should equal " 1s"
166+
167+ // Words with numbers
168+ toPlural " mp3" |> should equal " mp3s"
169+ toSingular " mp3s" |> should equal " mp3"
170+
171+ [<Test>]
172+ let ``Special edge cases from word list`` () =
173+ // Test some edge cases from the special words list
174+ toPlural " octopus" |> should equal " octopuses" // First plural form
175+ toSingular " octopuses" |> should equal " octopus"
176+ toSingular " octopodes" |> should equal " octopus" // Alternative plural
177+
178+ toPlural " focus" |> should equal " focuses" // First plural form
179+ toSingular " focuses" |> should equal " focus"
180+ toSingular " foci" |> should equal " focus" // Alternative plural
181+
182+ // Test words with multiple plural forms
183+ toPlural " fungus" |> should equal " fungi" // First plural form in list
184+ toSingular " fungi" |> should equal " fungus"
185+ toSingular " funguses" |> should equal " fungus" // Alternative plural
186+
187+ [<Test>]
188+ let ``Roundtrip consistency for common words`` () =
189+ let testWords = [
190+ " book" ; " house" ; " child" ; " mouse" ; " man" ; " woman"
191+ " city" ; " country" ; " company" ; " person" ; " foot" ; " tooth"
192+ " deer" ; " sheep" ; " fish" ; " aircraft" ; " series"
193+ ]
194+
195+ for word in testWords do
196+ let plural = toPlural word
197+ let backToSingular = toSingular plural
198+
199+ // For most words, singularizing the plural should get back the original
200+ // (This may not be true for all words due to multiple plural forms)
201+ if word <> " deer" && word <> " sheep" && word <> " fish" &&
202+ word <> " aircraft" && word <> " series" then
203+ backToSingular |> should equal word
204+
205+ [<Test>]
206+ let ``Performance with repeated calls`` () =
207+ // Test that repeated calls with same input work correctly (testing lazy initialization)
208+ let word = " house"
209+ let firstResult = toPlural word
210+ let secondResult = toPlural word
211+ let thirdResult = toPlural word
212+
213+ firstResult |> should equal " houses"
214+ secondResult |> should equal firstResult
215+ thirdResult |> should equal firstResult
216+
217+ // Same for singularization
218+ let pluralWord = " houses"
219+ let firstSingular = toSingular pluralWord
220+ let secondSingular = toSingular pluralWord
221+
222+ firstSingular |> should equal " house"
223+ secondSingular |> should equal firstSingular
0 commit comments