1package inflect
2
3import (
4	"testing"
5)
6
7// assert helper
8
9func assertEqual(t *testing.T, a, b string) {
10	if a != b {
11		t.Errorf("inflect: expected %v got %v", a, b)
12	}
13}
14
15// test data
16
17var SingularToPlural = map[string]string{
18	"search":      "searches",
19	"switch":      "switches",
20	"fix":         "fixes",
21	"box":         "boxes",
22	"process":     "processes",
23	"address":     "addresses",
24	"case":        "cases",
25	"stack":       "stacks",
26	"wish":        "wishes",
27	"fish":        "fish",
28	"jeans":       "jeans",
29	"funky jeans": "funky jeans",
30	"category":    "categories",
31	"query":       "queries",
32	"ability":     "abilities",
33	"agency":      "agencies",
34	"movie":       "movies",
35	"archive":     "archives",
36	"index":       "indices",
37	"wife":        "wives",
38	"safe":        "saves",
39	"half":        "halves",
40	"move":        "moves",
41	"salesperson": "salespeople",
42	"person":      "people",
43	"spokesman":   "spokesmen",
44	"man":         "men",
45	"woman":       "women",
46	"basis":       "bases",
47	"diagnosis":   "diagnoses",
48	"diagnosis_a": "diagnosis_as",
49	"datum":       "data",
50	"medium":      "media",
51	"stadium":     "stadia",
52	"analysis":    "analyses",
53	"node_child":  "node_children",
54	"child":       "children",
55	"experience":  "experiences",
56	"day":         "days",
57	"comment":     "comments",
58	"foobar":      "foobars",
59	"newsletter":  "newsletters",
60	"old_news":    "old_news",
61	"news":        "news",
62	"series":      "series",
63	"species":     "species",
64	"quiz":        "quizzes",
65	"perspective": "perspectives",
66	"ox":          "oxen",
67	"photo":       "photos",
68	"buffalo":     "buffaloes",
69	"tomato":      "tomatoes",
70	"dwarf":       "dwarves",
71	"elf":         "elves",
72	"information": "information",
73	"equipment":   "equipment",
74	"bus":         "buses",
75	"status":      "statuses",
76	"status_code": "status_codes",
77	"mouse":       "mice",
78	"louse":       "lice",
79	"house":       "houses",
80	"octopus":     "octopi",
81	"virus":       "viri",
82	"alias":       "aliases",
83	"portfolio":   "portfolios",
84	"vertex":      "vertices",
85	"matrix":      "matrices",
86	"matrix_fu":   "matrix_fus",
87	"axis":        "axes",
88	"testis":      "testes",
89	"crisis":      "crises",
90	"rice":        "rice",
91	"shoe":        "shoes",
92	"horse":       "horses",
93	"prize":       "prizes",
94	"edge":        "edges",
95	"database":    "databases",
96}
97
98var CapitalizeMixture = map[string]string{
99	"product":               "Product",
100	"special_guest":         "Special_guest",
101	"applicationController": "ApplicationController",
102	"Area51Controller":      "Area51Controller",
103}
104
105var CamelToUnderscore = map[string]string{
106	"Product":               "product",
107	"SpecialGuest":          "special_guest",
108	"ApplicationController": "application_controller",
109	"Area51Controller":      "area51_controller",
110}
111
112var UnderscoreToLowerCamel = map[string]string{
113	"product":                "product",
114	"special_guest":          "specialGuest",
115	"application_controller": "applicationController",
116	"area51_controller":      "area51Controller",
117}
118
119var CamelToUnderscoreWithoutReverse = map[string]string{
120	"HTMLTidy":          "html_tidy",
121	"HTMLTidyGenerator": "html_tidy_generator",
122	"FreeBsd":           "free_bsd",
123	"HTML":              "html",
124}
125
126var ClassNameToForeignKeyWithUnderscore = map[string]string{
127	"Person":  "person_id",
128	"Account": "account_id",
129}
130
131var PluralToForeignKeyWithUnderscore = map[string]string{
132	"people":   "person_id",
133	"accounts": "account_id",
134}
135
136var ClassNameToForeignKeyWithoutUnderscore = map[string]string{
137	"Person":  "personid",
138	"Account": "accountid",
139}
140
141var ClassNameToTableName = map[string]string{
142	"PrimarySpokesman": "primary_spokesmen",
143	"NodeChild":        "node_children",
144}
145
146var StringToParameterized = map[string]string{
147	"Donald E. Knuth":                     "donald-e-knuth",
148	"Random text with *(bad)* characters": "random-text-with-bad-characters",
149	"Allow_Under_Scores":                  "allow_under_scores",
150	"Trailing bad characters!@#":          "trailing-bad-characters",
151	"!@#Leading bad characters":           "leading-bad-characters",
152	"Squeeze   separators":                "squeeze-separators",
153	"Test with + sign":                    "test-with-sign",
154	"Test with malformed utf8 \251":       "test-with-malformed-utf8",
155}
156
157var StringToParameterizeWithNoSeparator = map[string]string{
158	"Donald E. Knuth":                     "donaldeknuth",
159	"With-some-dashes":                    "with-some-dashes",
160	"Random text with *(bad)* characters": "randomtextwithbadcharacters",
161	"Trailing bad characters!@#":          "trailingbadcharacters",
162	"!@#Leading bad characters":           "leadingbadcharacters",
163	"Squeeze   separators":                "squeezeseparators",
164	"Test with + sign":                    "testwithsign",
165	"Test with malformed utf8 \251":       "testwithmalformedutf8",
166}
167
168var StringToParameterizeWithUnderscore = map[string]string{
169	"Donald E. Knuth":                     "donald_e_knuth",
170	"Random text with *(bad)* characters": "random_text_with_bad_characters",
171	"With-some-dashes":                    "with-some-dashes",
172	"Retain_underscore":                   "retain_underscore",
173	"Trailing bad characters!@#":          "trailing_bad_characters",
174	"!@#Leading bad characters":           "leading_bad_characters",
175	"Squeeze   separators":                "squeeze_separators",
176	"Test with + sign":                    "test_with_sign",
177	"Test with malformed utf8 \251":       "test_with_malformed_utf8",
178}
179
180var StringToParameterizedAndNormalized = map[string]string{
181	"Malmö":         "malmo",
182	"Garçons":       "garcons",
183	"Opsů":          "opsu",
184	"Ærøskøbing":    "aeroskobing",
185	"Aßlar":         "asslar",
186	"Japanese: 日本語": "japanese",
187}
188
189var UnderscoreToHuman = map[string]string{
190	"employee_salary": "Employee salary",
191	"employee_id":     "Employee",
192	"underground":     "Underground",
193}
194
195var MixtureToTitleCase = map[string]string{
196	"active_record":      "Active Record",
197	"ActiveRecord":       "Active Record",
198	"action web service": "Action Web Service",
199	"Action Web Service": "Action Web Service",
200	"Action web service": "Action Web Service",
201	"actionwebservice":   "Actionwebservice",
202	"Actionwebservice":   "Actionwebservice",
203	"david's code":       "David's Code",
204	"David's code":       "David's Code",
205	"david's Code":       "David's Code",
206}
207
208var OrdinalNumbers = map[string]string{
209	"-1":    "-1st",
210	"-2":    "-2nd",
211	"-3":    "-3rd",
212	"-4":    "-4th",
213	"-5":    "-5th",
214	"-6":    "-6th",
215	"-7":    "-7th",
216	"-8":    "-8th",
217	"-9":    "-9th",
218	"-10":   "-10th",
219	"-11":   "-11th",
220	"-12":   "-12th",
221	"-13":   "-13th",
222	"-14":   "-14th",
223	"-20":   "-20th",
224	"-21":   "-21st",
225	"-22":   "-22nd",
226	"-23":   "-23rd",
227	"-24":   "-24th",
228	"-100":  "-100th",
229	"-101":  "-101st",
230	"-102":  "-102nd",
231	"-103":  "-103rd",
232	"-104":  "-104th",
233	"-110":  "-110th",
234	"-111":  "-111th",
235	"-112":  "-112th",
236	"-113":  "-113th",
237	"-1000": "-1000th",
238	"-1001": "-1001st",
239	"0":     "0th",
240	"1":     "1st",
241	"2":     "2nd",
242	"3":     "3rd",
243	"4":     "4th",
244	"5":     "5th",
245	"6":     "6th",
246	"7":     "7th",
247	"8":     "8th",
248	"9":     "9th",
249	"10":    "10th",
250	"11":    "11th",
251	"12":    "12th",
252	"13":    "13th",
253	"14":    "14th",
254	"20":    "20th",
255	"21":    "21st",
256	"22":    "22nd",
257	"23":    "23rd",
258	"24":    "24th",
259	"100":   "100th",
260	"101":   "101st",
261	"102":   "102nd",
262	"103":   "103rd",
263	"104":   "104th",
264	"110":   "110th",
265	"111":   "111th",
266	"112":   "112th",
267	"113":   "113th",
268	"1000":  "1000th",
269	"1001":  "1001st",
270}
271
272var UnderscoresToDashes = map[string]string{
273	"street":                "street",
274	"street_address":        "street-address",
275	"person_street_address": "person-street-address",
276}
277
278var Irregularities = map[string]string{
279	"person": "people",
280	"man":    "men",
281	"child":  "children",
282	"sex":    "sexes",
283	"move":   "moves",
284}
285
286type AcronymCase struct {
287	camel string
288	under string
289	human string
290	title string
291}
292
293var AcronymCases = []*AcronymCase{
294	//           camelize             underscore            humanize              titleize
295	&AcronymCase{"API", "api", "API", "API"},
296	&AcronymCase{"APIController", "api_controller", "API controller", "API Controller"},
297	&AcronymCase{"Nokogiri::HTML", "nokogiri/html", "Nokogiri/HTML", "Nokogiri/HTML"},
298	&AcronymCase{"HTTPAPI", "http_api", "HTTP API", "HTTP API"},
299	&AcronymCase{"HTTP::Get", "http/get", "HTTP/get", "HTTP/Get"},
300	&AcronymCase{"SSLError", "ssl_error", "SSL error", "SSL Error"},
301	&AcronymCase{"RESTful", "restful", "RESTful", "RESTful"},
302	&AcronymCase{"RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"},
303	&AcronymCase{"IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"},
304	&AcronymCase{"PhDRequired", "phd_required", "PhD required", "PhD Required"},
305	&AcronymCase{"IRoRU", "i_ror_u", "I RoR u", "I RoR U"},
306	&AcronymCase{"RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"},
307	// misdirection
308	&AcronymCase{"Capistrano", "capistrano", "Capistrano", "Capistrano"},
309	&AcronymCase{"CapiController", "capi_controller", "Capi controller", "Capi Controller"},
310	&AcronymCase{"HttpsApis", "https_apis", "Https apis", "Https Apis"},
311	&AcronymCase{"Html5", "html5", "Html5", "Html5"},
312	&AcronymCase{"Restfully", "restfully", "Restfully", "Restfully"},
313	&AcronymCase{"RoRails", "ro_rails", "Ro rails", "Ro Rails"},
314}
315
316// tests
317
318func TestPluralizePlurals(t *testing.T) {
319	assertEqual(t, "plurals", Pluralize("plurals"))
320	assertEqual(t, "Plurals", Pluralize("Plurals"))
321}
322
323func TestPluralizeEmptyString(t *testing.T) {
324	assertEqual(t, "", Pluralize(""))
325}
326
327func TestUncountables(t *testing.T) {
328	for word := range Uncountables() {
329		assertEqual(t, word, Singularize(word))
330		assertEqual(t, word, Pluralize(word))
331		assertEqual(t, Pluralize(word), Singularize(word))
332	}
333}
334
335func TestUncountableWordIsNotGreedy(t *testing.T) {
336	uncountableWord := "ors"
337	countableWord := "sponsor"
338
339	AddUncountable(uncountableWord)
340
341	assertEqual(t, uncountableWord, Singularize(uncountableWord))
342	assertEqual(t, uncountableWord, Pluralize(uncountableWord))
343	assertEqual(t, Pluralize(uncountableWord), Singularize(uncountableWord))
344	assertEqual(t, "sponsor", Singularize(countableWord))
345	assertEqual(t, "sponsors", Pluralize(countableWord))
346	assertEqual(t, "sponsor", Singularize(Pluralize(countableWord)))
347}
348
349func TestPluralizeSingular(t *testing.T) {
350	for singular, plural := range SingularToPlural {
351		assertEqual(t, plural, Pluralize(singular))
352		assertEqual(t, Capitalize(plural), Capitalize(Pluralize(singular)))
353	}
354}
355
356func TestSingularizePlural(t *testing.T) {
357	for singular, plural := range SingularToPlural {
358		assertEqual(t, singular, Singularize(plural))
359		assertEqual(t, Capitalize(singular), Capitalize(Singularize(plural)))
360	}
361}
362
363func TestPluralizePlural(t *testing.T) {
364	for _, plural := range SingularToPlural {
365		assertEqual(t, plural, Pluralize(plural))
366		assertEqual(t, Capitalize(plural), Capitalize(Pluralize(plural)))
367	}
368}
369
370func TestOverwritePreviousInflectors(t *testing.T) {
371	assertEqual(t, "series", Singularize("series"))
372	AddSingular("series", "serie")
373	assertEqual(t, "serie", Singularize("series"))
374	AddUncountable("series") // reset
375}
376
377func TestTitleize(t *testing.T) {
378	for before, titleized := range MixtureToTitleCase {
379		assertEqual(t, titleized, Titleize(before))
380	}
381}
382
383func TestCapitalize(t *testing.T) {
384	for lower, capitalized := range CapitalizeMixture {
385		assertEqual(t, capitalized, Capitalize(lower))
386	}
387}
388
389func TestCamelize(t *testing.T) {
390	for camel, underscore := range CamelToUnderscore {
391		assertEqual(t, camel, Camelize(underscore))
392	}
393}
394
395func TestCamelizeWithLowerDowncasesTheFirstLetter(t *testing.T) {
396	assertEqual(t, "capital", CamelizeDownFirst("Capital"))
397}
398
399func TestCamelizeWithUnderscores(t *testing.T) {
400	assertEqual(t, "CamelCase", Camelize("Camel_Case"))
401}
402
403// func TestAcronyms(t *testing.T) {
404//     AddAcronym("API")
405//     AddAcronym("HTML")
406//     AddAcronym("HTTP")
407//     AddAcronym("RESTful")
408//     AddAcronym("W3C")
409//     AddAcronym("PhD")
410//     AddAcronym("RoR")
411//     AddAcronym("SSL")
412//     // each in table
413//     for _,x := range AcronymCases {
414//         assertEqual(t, x.camel, Camelize(x.under))
415//         assertEqual(t, x.camel, Camelize(x.camel))
416//         assertEqual(t, x.under, Underscore(x.under))
417//         assertEqual(t, x.under, Underscore(x.camel))
418//         assertEqual(t, x.title, Titleize(x.under))
419//         assertEqual(t, x.title, Titleize(x.camel))
420//         assertEqual(t, x.human, Humanize(x.under))
421//     }
422// }
423
424// func TestAcronymOverride(t *testing.T) {
425//     AddAcronym("API")
426//     AddAcronym("LegacyApi")
427//     assertEqual(t, "LegacyApi", Camelize("legacyapi"))
428//     assertEqual(t, "LegacyAPI", Camelize("legacy_api"))
429//     assertEqual(t, "SomeLegacyApi", Camelize("some_legacyapi"))
430//     assertEqual(t, "Nonlegacyapi", Camelize("nonlegacyapi"))
431// }
432
433// func TestAcronymsCamelizeLower(t *testing.T) {
434//     AddAcronym("API")
435//     AddAcronym("HTML")
436//     assertEqual(t, "htmlAPI", CamelizeDownFirst("html_api"))
437//     assertEqual(t, "htmlAPI", CamelizeDownFirst("htmlAPI"))
438//     assertEqual(t, "htmlAPI", CamelizeDownFirst("HTMLAPI"))
439// }
440
441func TestUnderscoreAcronymSequence(t *testing.T) {
442	AddAcronym("API")
443	AddAcronym("HTML5")
444	AddAcronym("HTML")
445	assertEqual(t, "html5_html_api", Underscore("HTML5HTMLAPI"))
446}
447
448func TestUnderscore(t *testing.T) {
449	for camel, underscore := range CamelToUnderscore {
450		assertEqual(t, underscore, Underscore(camel))
451	}
452	for camel, underscore := range CamelToUnderscoreWithoutReverse {
453		assertEqual(t, underscore, Underscore(camel))
454	}
455}
456
457func TestForeignKey(t *testing.T) {
458	for klass, foreignKey := range ClassNameToForeignKeyWithUnderscore {
459		assertEqual(t, foreignKey, ForeignKey(klass))
460	}
461	for word, foreignKey := range PluralToForeignKeyWithUnderscore {
462		assertEqual(t, foreignKey, ForeignKey(word))
463	}
464	for klass, foreignKey := range ClassNameToForeignKeyWithoutUnderscore {
465		assertEqual(t, foreignKey, ForeignKeyCondensed(klass))
466	}
467}
468
469func TestTableize(t *testing.T) {
470	for klass, table := range ClassNameToTableName {
471		assertEqual(t, table, Tableize(klass))
472	}
473}
474
475func TestParameterize(t *testing.T) {
476	for str, parameterized := range StringToParameterized {
477		assertEqual(t, parameterized, Parameterize(str))
478	}
479}
480
481func TestParameterizeAndNormalize(t *testing.T) {
482	for str, parameterized := range StringToParameterizedAndNormalized {
483		assertEqual(t, parameterized, Parameterize(str))
484	}
485}
486
487func TestParameterizeWithCustomSeparator(t *testing.T) {
488	for str, parameterized := range StringToParameterizeWithUnderscore {
489		assertEqual(t, parameterized, ParameterizeJoin(str, "_"))
490	}
491}
492
493func TestTypeify(t *testing.T) {
494	for klass, table := range ClassNameToTableName {
495		assertEqual(t, klass, Typeify(table))
496		assertEqual(t, klass, Typeify("table_prefix."+table))
497	}
498}
499
500func TestTypeifyWithLeadingSchemaName(t *testing.T) {
501	assertEqual(t, "FooBar", Typeify("schema.foo_bar"))
502}
503
504func TestHumanize(t *testing.T) {
505	for underscore, human := range UnderscoreToHuman {
506		assertEqual(t, human, Humanize(underscore))
507	}
508}
509
510func TestHumanizeByString(t *testing.T) {
511	AddHuman("col_rpted_bugs", "reported bugs")
512	assertEqual(t, "90 reported bugs recently", Humanize("90 col_rpted_bugs recently"))
513}
514
515func TestOrdinal(t *testing.T) {
516	for number, ordinalized := range OrdinalNumbers {
517		assertEqual(t, ordinalized, Ordinalize(number))
518	}
519}
520
521func TestDasherize(t *testing.T) {
522	for underscored, dasherized := range UnderscoresToDashes {
523		assertEqual(t, dasherized, Dasherize(underscored))
524	}
525}
526
527func TestUnderscoreAsReverseOfDasherize(t *testing.T) {
528	for underscored := range UnderscoresToDashes {
529		assertEqual(t, underscored, Underscore(Dasherize(underscored)))
530	}
531}
532
533func TestUnderscoreToLowerCamel(t *testing.T) {
534	for underscored, lower := range UnderscoreToLowerCamel {
535		assertEqual(t, lower, CamelizeDownFirst(underscored))
536	}
537}
538
539func Test_clear_all(t *testing.T) {
540	// test a way of resetting inflexions
541}
542
543func TestIrregularityBetweenSingularAndPlural(t *testing.T) {
544	for singular, plural := range Irregularities {
545		AddIrregular(singular, plural)
546		assertEqual(t, singular, Singularize(plural))
547		assertEqual(t, plural, Pluralize(singular))
548	}
549}
550
551func TestPluralizeOfIrregularity(t *testing.T) {
552	for singular, plural := range Irregularities {
553		AddIrregular(singular, plural)
554		assertEqual(t, plural, Pluralize(plural))
555	}
556}
557