slug_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. // Copyright 2013 by Dobrosław Żybort. All rights reserved.
  2. // This Source Code Form is subject to the terms of the Mozilla Public
  3. // License, v. 2.0. If a copy of the MPL was not distributed with this
  4. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  5. package slug
  6. import (
  7. "testing"
  8. )
  9. //=============================================================================
  10. func TestSlugMake(t *testing.T) {
  11. var testCases = []struct {
  12. in string
  13. want string
  14. }{
  15. {"DOBROSLAWZYBORT", "dobroslawzybort"},
  16. {"Dobroslaw Zybort", "dobroslaw-zybort"},
  17. {" Dobroslaw Zybort ?", "dobroslaw-zybort"},
  18. {"Dobrosław Żybort", "dobroslaw-zybort"},
  19. {"Ala ma 6 kotów.", "ala-ma-6-kotow"},
  20. {"áÁàÀãÃâÂäÄąĄą̊Ą̊", "aaaaaaaaaaaaaa"},
  21. {"ćĆĉĈçÇ", "cccccc"},
  22. {"éÉèÈẽẼêÊëËęĘ", "eeeeeeeeeeee"},
  23. {"íÍìÌĩĨîÎïÏįĮ", "iiiiiiiiiiii"},
  24. {"łŁ", "ll"},
  25. {"ńŃ", "nn"},
  26. {"óÓòÒõÕôÔöÖǫǪǭǬø", "ooooooooooooooo"},
  27. {"śŚ", "ss"},
  28. {"úÚùÙũŨûÛüÜųŲ", "uuuuuuuuuuuu"},
  29. {"y̨Y̨", "yy"},
  30. {"źŹżŹ", "zzzz"},
  31. {"·/,:;`˜'\"", ""},
  32. {"2000–2013", "2000-2013"},
  33. {"style—not", "style-not"},
  34. {"test_slug", "test_slug"},
  35. {"Æ", "ae"},
  36. {"Ich heiße", "ich-heisse"},
  37. {"This & that", "this-and-that"},
  38. {"fácil €", "facil-eu"},
  39. {"smile ☺", "smile"},
  40. {"Hellö Wörld хелло ворлд", "hello-world-khello-vorld"},
  41. {"\"C'est déjà l’été.\"", "cest-deja-lete"},
  42. {"jaja---lol-méméméoo--a", "jaja-lol-mememeoo-a"},
  43. {"影師", "ying-shi"},
  44. }
  45. for index, st := range testCases {
  46. got := Make(st.in)
  47. if got != st.want {
  48. t.Errorf(
  49. "%d. Make(%#v) = %#v; want %#v",
  50. index, st.in, got, st.want)
  51. }
  52. }
  53. }
  54. func TestSlugMakeLang(t *testing.T) {
  55. var testCases = []struct {
  56. lang string
  57. in string
  58. want string
  59. }{
  60. {"en", "This & that", "this-and-that"},
  61. {"de", "This & that", "this-und-that"},
  62. {"pl", "This & that", "this-i-that"},
  63. {"es", "This & that", "this-y-that"},
  64. {"gr", "This & that", "this-kai-that"},
  65. {"test", "This & that", "this-and-that"}, // unknown lang, fallback to "en"
  66. }
  67. for index, smlt := range testCases {
  68. got := MakeLang(smlt.in, smlt.lang)
  69. if got != smlt.want {
  70. t.Errorf(
  71. "%d. MakeLang(%#v, %#v) = %#v; want %#v",
  72. index, smlt.in, smlt.lang, got, smlt.want)
  73. }
  74. }
  75. }
  76. func TestSlugMakeUserSubstituteLang(t *testing.T) {
  77. var testCases = []struct {
  78. cSub map[string]string
  79. lang string
  80. in string
  81. want string
  82. }{
  83. {map[string]string{"'": " "}, "en", "That's great", "that-s-great"},
  84. {map[string]string{"&": "or"}, "en", "This & that", "this-or-that"}, // by default "&" => "and"
  85. {map[string]string{"&": "or"}, "de", "This & that", "this-or-that"}, // by default "&" => "und"
  86. {map[string]string{"&": "or", "@": "the"}, "de", "@ This & that", "the-this-or-that"}, // by default "&" => "und", "@" => "an"
  87. }
  88. for index, smust := range testCases {
  89. CustomSub = smust.cSub
  90. got := MakeLang(smust.in, smust.lang)
  91. if got != smust.want {
  92. t.Errorf(
  93. "%d. %#v; MakeLang(%#v, %#v) = %#v; want %#v",
  94. index, smust.cSub, smust.in, smust.lang,
  95. got, smust.want)
  96. }
  97. }
  98. }
  99. func TestSlugMakeSubstituteOrderLang(t *testing.T) {
  100. // Always substitute runes first
  101. var testCases = []struct {
  102. rSub map[rune]string
  103. sSub map[string]string
  104. in string
  105. want string
  106. }{
  107. {map[rune]string{'o': "left"}, map[string]string{"o": "right"}, "o o", "left-left"},
  108. {map[rune]string{'o': "left", 'a': "r"}, map[string]string{"o": "right"}, "o a o", "left-r-left"},
  109. {map[rune]string{'o': "left"}, map[string]string{"o": "right", "a": "r"}, "a o a o", "r-left-r-left"},
  110. {map[rune]string{'&': "down"}, map[string]string{"&": "up"}, "&", "down"},
  111. }
  112. for index, smsot := range testCases {
  113. CustomRuneSub = smsot.rSub
  114. CustomSub = smsot.sSub
  115. got := Make(smsot.in)
  116. if got != smsot.want {
  117. t.Errorf(
  118. "%d. %#v; %#v; Make(%#v) = %#v; want %#v",
  119. index, smsot.rSub, smsot.sSub, smsot.in,
  120. got, smsot.want)
  121. }
  122. }
  123. }
  124. func TestSubstituteLang(t *testing.T) {
  125. var testCases = []struct {
  126. cSub map[string]string
  127. in string
  128. want string
  129. }{
  130. {map[string]string{"o": "no"}, "o o o", "no no no"},
  131. {map[string]string{"o": "no", "a": "or"}, "o a o", "no nor no"},
  132. {map[string]string{"a": "or", "o": "no"}, "o a o", "no nor no"},
  133. {map[string]string{"'": " "}, "That's great", "That s great"},
  134. }
  135. for index, sst := range testCases {
  136. got := Substitute(sst.in, sst.cSub)
  137. if got != sst.want {
  138. t.Errorf(
  139. "%d. Substitute(%#v, %#v) = %#v; want %#v",
  140. index, sst.in, sst.cSub, got, sst.want)
  141. }
  142. }
  143. }
  144. func TestSubstituteRuneLang(t *testing.T) {
  145. var testCases = []struct {
  146. cSub map[rune]string
  147. in string
  148. want string
  149. }{
  150. {map[rune]string{'o': "no"}, "o o o", "no no no"},
  151. {map[rune]string{'o': "no", 'a': "or"}, "o a o", "no or no"},
  152. {map[rune]string{'a': "or", 'o': "no"}, "o a o", "no or no"},
  153. {map[rune]string{'\'': " "}, "That's great", "That s great"},
  154. }
  155. for index, ssrt := range testCases {
  156. got := SubstituteRune(ssrt.in, ssrt.cSub)
  157. if got != ssrt.want {
  158. t.Errorf(
  159. "%d. SubstituteRune(%#v, %#v) = %#v; want %#v",
  160. index, ssrt.in, ssrt.cSub, got, ssrt.want)
  161. }
  162. }
  163. }
  164. func TestSlugMakeSmartTruncate(t *testing.T) {
  165. var testCases = []struct {
  166. in string
  167. maxLength int
  168. want string
  169. }{
  170. {"DOBROSLAWZYBORT", 100, "dobroslawzybort"},
  171. {"Dobroslaw Zybort", 100, "dobroslaw-zybort"},
  172. {"Dobroslaw Zybort", 12, "dobroslaw"},
  173. {" Dobroslaw Zybort ?", 12, "dobroslaw"},
  174. {"Ala ma 6 kotów.", 10, "ala-ma-6"},
  175. {"Dobrosław Żybort", 5, "dobro"},
  176. }
  177. for index, smstt := range testCases {
  178. MaxLength = smstt.maxLength
  179. got := Make(smstt.in)
  180. if got != smstt.want {
  181. t.Errorf(
  182. "%d. MaxLength = %v; Make(%#v) = %#v; want %#v",
  183. index, smstt.maxLength, smstt.in, got, smstt.want)
  184. }
  185. }
  186. }
  187. func TestIsSlug(t *testing.T) {
  188. MaxLength = 0
  189. type args struct {
  190. text string
  191. }
  192. tests := []struct {
  193. name string
  194. args args
  195. want bool
  196. }{
  197. {"some", args{"some"}, true},
  198. {"with -", args{"some-more"}, true},
  199. {"with _", args{"some_more"}, true},
  200. {"with numbers", args{"number-2"}, true},
  201. {"empty string", args{""}, false},
  202. {"upper case", args{"Some-more"}, false},
  203. {"space", args{"some more"}, false},
  204. {"starts with '-'", args{"-some"}, false},
  205. {"ends with '-'", args{"some-"}, false},
  206. {"starts with '_'", args{"_some"}, false},
  207. {"ends with '_'", args{"some_"}, false},
  208. {"outside ASCII", args{"Dobrosław Żybort"}, false},
  209. {"outside ASCII –", args{"2000–2013"}, false},
  210. {"smile ☺", args{"smile ☺"}, false},
  211. }
  212. for _, tt := range tests {
  213. t.Run(tt.name, func(t *testing.T) {
  214. if got := IsSlug(tt.args.text); got != tt.want {
  215. t.Errorf("IsSlug() = %v, want %v", got, tt.want)
  216. }
  217. })
  218. }
  219. t.Run("MaxLength", func(t *testing.T) {
  220. MaxLength = 4
  221. if got := IsSlug("012345"); got != false {
  222. t.Errorf("IsSlug() = %v, want %v", got, false)
  223. }
  224. MaxLength = 0
  225. })
  226. }
  227. func BenchmarkMakeShortAscii(b *testing.B) {
  228. b.ReportAllocs()
  229. for n := 0; n < b.N; n++ {
  230. Make("Hello world")
  231. }
  232. }
  233. func BenchmarkMakeShort(b *testing.B) {
  234. b.ReportAllocs()
  235. for n := 0; n < b.N; n++ {
  236. Make("хелло ворлд")
  237. }
  238. }
  239. func BenchmarkMakeShortSymbols(b *testing.B) {
  240. b.ReportAllocs()
  241. for n := 0; n < b.N; n++ {
  242. Make("·/,:;`˜'\" &€£¥")
  243. }
  244. }
  245. func BenchmarkMakeMediumAscii(b *testing.B) {
  246. b.ReportAllocs()
  247. for n := 0; n < b.N; n++ {
  248. Make("ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE")
  249. }
  250. }
  251. func BenchmarkMakeMedium(b *testing.B) {
  252. b.ReportAllocs()
  253. for n := 0; n < b.N; n++ {
  254. Make("ヲァィゥェ ォャュョッ ーアイウエ オカキクケ コサシスセ ソタチツテ トナニヌネ ノハヒフヘ ホマミムメ モヤユヨラ リルレロワ")
  255. }
  256. }
  257. func BenchmarkMakeLongAscii(b *testing.B) {
  258. longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
  259. "pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
  260. "sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
  261. "odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
  262. "elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
  263. "pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
  264. "elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
  265. "sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
  266. "sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
  267. "ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
  268. "magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
  269. "ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
  270. "nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
  271. "ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
  272. "Integer eros libero, mollis sed posuere."
  273. b.ReportAllocs()
  274. b.ResetTimer()
  275. for n := 0; n < b.N; n++ {
  276. Make(longStr)
  277. }
  278. }
  279. func BenchmarkSubstituteRuneShort(b *testing.B) {
  280. shortStr := "Hello/Hi world"
  281. subs := map[rune]string{'o': "no", '/': "slash"}
  282. b.ReportAllocs()
  283. b.ResetTimer()
  284. for n := 0; n < b.N; n++ {
  285. SubstituteRune(shortStr, subs)
  286. }
  287. }
  288. func BenchmarkSubstituteRuneLong(b *testing.B) {
  289. longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
  290. "pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
  291. "sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
  292. "odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
  293. "elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
  294. "pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
  295. "elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
  296. "sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
  297. "sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
  298. "ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
  299. "magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
  300. "ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
  301. "nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
  302. "ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
  303. "Integer eros libero, mollis sed posuere."
  304. subs := map[rune]string{
  305. 'o': "no",
  306. '/': "slash",
  307. 'i': "done",
  308. 'E': "es",
  309. 'a': "ASD",
  310. '1': "one",
  311. 'l': "onetwo",
  312. }
  313. b.ReportAllocs()
  314. b.ResetTimer()
  315. for n := 0; n < b.N; n++ {
  316. SubstituteRune(longStr, subs)
  317. }
  318. }
  319. func BenchmarkSmartTruncateShort(b *testing.B) {
  320. shortStr := "Hello-world"
  321. MaxLength = 8
  322. b.ReportAllocs()
  323. b.ResetTimer()
  324. for n := 0; n < b.N; n++ {
  325. smartTruncate(shortStr)
  326. }
  327. }
  328. func BenchmarkSmartTruncateLong(b *testing.B) {
  329. longStr := "Lorem-ipsum-dolor-sit-amet,-consectetur-adipiscing-elit.-Morbi-" +
  330. "pulvinar-sodales-ultrices.-Nulla-facilisi.-Sed-at-vestibulum-erat.-Ut-" +
  331. "sit-amet-urna-posuere,-sagittis-eros-ac,-varius-nisi.-Morbi-ullamcorper-" +
  332. "odio-at-nunc-pulvinar-mattis.-Vestibulum-rutrum,-ante-eu-dictum-mattis,-" +
  333. "elit-risus-finibus-nunc,-consectetur-facilisis-eros-leo-ut-sapien.-Sed-" +
  334. "pulvinar-volutpat-mi.-Cras-semper-mi-ac-eros-accumsan,-at-feugiat-massa-" +
  335. "elementum.-Morbi-eget-dolor-sit-amet-purus-condimentum-egestas-non-ut-" +
  336. "sapien.-Duis-feugiat-magna-vitae-nisi-lobortis,-quis-finibus-sem-" +
  337. "sollicitudin.-Pellentesque-eleifend-blandit-ipsum,-ut-porta-arcu-" +
  338. "ultricies-et.-Fusce-vel-ipsum-porta,-placerat-diam-ac,-consectetur-" +
  339. "magna.-Nulla-in-porta-sem.-Suspendisse-commodo,-felis-in-molestie-" +
  340. "ultricies,-arcu-ipsum-aliquet-turpis,-elementum-dapibus-ipsum-lorem-a-" +
  341. "nisl.-Etiam-varius-imperdiet-placerat.-Aliquam-euismod-lacus-arcu,-" +
  342. "ultrices-hendrerit-est-pellentesque-vel.-Aliquam-sit-amet-laoreet-leo.-" +
  343. "Integer-eros-libero,-mollis-sed-posuere."
  344. MaxLength = 256
  345. b.ReportAllocs()
  346. b.ResetTimer()
  347. for n := 0; n < b.N; n++ {
  348. smartTruncate(longStr)
  349. }
  350. }
  351. func BenchmarkIsSlugShort(b *testing.B) {
  352. shortStr := "hello-world"
  353. b.ReportAllocs()
  354. b.ResetTimer()
  355. for n := 0; n < b.N; n++ {
  356. IsSlug(shortStr)
  357. }
  358. }
  359. func BenchmarkIsSlugLong(b *testing.B) {
  360. longStr := "lorem-ipsum-dolor-sit-amet-consectetur-adipiscing-elit-morbi-" +
  361. "pulvinar-sodales-ultrices-nulla-facilisi-sed-at-vestibulum-erat-ut-" +
  362. "sit-amet-urna-posuere-sagittis-eros-ac-varius-nisi-morbi-ullamcorper-" +
  363. "odio-at-nunc-pulvinar-mattis-vestibulum-rutrum-ante-eu-dictum-mattis,-" +
  364. "elit-risus-finibus-nunc-consectetur-facilisis-eros-leo-ut-sapien-sed-" +
  365. "pulvinar-volutpat-mi-cras-semper-mi-ac-eros-accumsan-at-feugiat-massa-" +
  366. "elementum-morbi-eget-dolor-sit-amet-purus-condimentum-egestas-non-ut-" +
  367. "sapien-duis-feugiat-magna-vitae-nisi-lobortis-quis-finibus-sem-" +
  368. "sollicitudin-pellentesque-eleifend-blandit-ipsum-ut-porta-arcu-" +
  369. "ultricies-et-fusce-vel-ipsum-porta-placerat-diam-ac-consectetur-" +
  370. "magna-nulla-in-porta-sem-suspendisse-commodo-felis-in-molestie-" +
  371. "ultricies-arcu-ipsum-aliquet-turpis-elementum-dapibus-ipsum-lorem-a-" +
  372. "nisl-etiam-varius-imperdiet-placerat-aliquam-euismod-lacus-arcu-" +
  373. "ultrices-hendrerit-est-pellentesque-vel-aliquam-sit-amet-laoreet-leo-" +
  374. "integer-eros-libero-mollis-sed-posuere"
  375. b.ReportAllocs()
  376. b.ResetTimer()
  377. for n := 0; n < b.N; n++ {
  378. IsSlug(longStr)
  379. }
  380. }