github.com/brandur/modulir@v0.0.0-20240305213423-94ee82929cbd/modules/mtemplate/mtemplate_test.go (about) 1 package mtemplate 2 3 import ( 4 "context" 5 "html/template" 6 "net/url" 7 "strings" 8 "testing" 9 "time" 10 11 assert "github.com/stretchr/testify/require" 12 ) 13 14 var testTime time.Time 15 16 func init() { 17 const longForm = "2006/01/02 15:04" 18 var err error 19 testTime, err = time.Parse(longForm, "2016/07/03 12:34") 20 if err != nil { 21 panic(err) 22 } 23 } 24 25 func TestCollapseHTML(t *testing.T) { 26 assert.Equal(t, "<p><strong>strong</strong></p>", collapseHTML(` 27 <p> 28 <strong>strong</strong> 29 </p>`)) 30 } 31 32 func TestCollapseParagraphs(t *testing.T) { 33 assert.Equal(t, "<strong>strong</strong>", CollapseParagraphs(` 34 <p> 35 <strong>strong</strong> 36 </p> 37 <p> 38 </p>`)) 39 } 40 41 func TestCombineFuncMaps(t *testing.T) { 42 fm1 := template.FuncMap{ 43 "CollapseParagraphs": CollapseParagraphs, 44 } 45 fm2 := template.FuncMap{ 46 "QueryEscape": QueryEscape, 47 } 48 fm3 := template.FuncMap{ 49 "To2X": To2X, 50 } 51 52 combined := CombineFuncMaps(fm1, fm2, fm3) 53 54 { 55 _, ok := combined["CollapseParagraphs"] 56 assert.True(t, ok) 57 } 58 { 59 _, ok := combined["QueryEscape"] 60 assert.True(t, ok) 61 } 62 { 63 _, ok := combined["To2X"] 64 assert.True(t, ok) 65 } 66 } 67 68 func TestCombineFuncMaps_Duplicate(t *testing.T) { 69 fm1 := template.FuncMap{ 70 "CollapseParagraphs": CollapseParagraphs, 71 } 72 fm2 := template.FuncMap{ 73 "CollapseParagraphs": CollapseParagraphs, 74 } 75 76 assert.PanicsWithError(t, 77 "duplicate function map key on combine: CollapseParagraphs", func() { 78 _ = CombineFuncMaps(fm1, fm2) 79 }) 80 } 81 82 func TestHTMLFuncMapToText(t *testing.T) { 83 fm := template.FuncMap{ 84 "To2X": To2X, 85 } 86 87 textFM := HTMLFuncMapToText(fm) 88 89 { 90 _, ok := textFM["To2X"] 91 assert.True(t, ok) 92 } 93 } 94 95 func TestDistanceOfTimeInWords(t *testing.T) { 96 to := time.Now() 97 98 assert.Equal(t, "less than 1 minute", 99 DistanceOfTimeInWords(to.Add(mustParseDuration("-1s")), to)) 100 assert.Equal(t, "1 minute", 101 DistanceOfTimeInWords(to.Add(mustParseDuration("-1m")), to)) 102 assert.Equal(t, "8 minutes", 103 DistanceOfTimeInWords(to.Add(mustParseDuration("-8m")), to)) 104 assert.Equal(t, "about 1 hour", 105 DistanceOfTimeInWords(to.Add(mustParseDuration("-52m")), to)) 106 assert.Equal(t, "about 3 hours", 107 DistanceOfTimeInWords(to.Add(mustParseDuration("-3h")), to)) 108 assert.Equal(t, "about 1 day", 109 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")), to)) 110 111 // note that parse only handles up to "h" units 112 assert.Equal(t, "9 days", 113 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*9), to)) 114 assert.Equal(t, "about 1 month", 115 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*30), to)) 116 assert.Equal(t, "4 months", 117 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*30*4), to)) 118 assert.Equal(t, "about 1 year", 119 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*365), to)) 120 assert.Equal(t, "about 1 year", 121 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*(365+2*30)), to)) 122 assert.Equal(t, "over 1 year", 123 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*(365+3*30)), to)) 124 assert.Equal(t, "almost 2 years", 125 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*(365+10*30)), to)) 126 assert.Equal(t, "2 years", 127 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*(365*2)), to)) 128 assert.Equal(t, "3 years", 129 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*(365*3)), to)) 130 assert.Equal(t, "10 years", 131 DistanceOfTimeInWords(to.Add(mustParseDuration("-24h")*(365*10)), to)) 132 } 133 134 func TestDownloadedImage(t *testing.T) { 135 ctx := context.Background() 136 137 t.Run("SetsContextAndEmitsPath", func(t *testing.T) { 138 ctx, downloadedImageContainer := DownloadedImageContext(ctx) 139 140 assert.Equal(t, 141 "/photographs/belize/01/kukumba-beach-1.jpg", 142 DownloadedImage( 143 ctx, 144 "/photographs/belize/01/kukumba-beach-1", 145 "https://www.dropbox.com/s/6fmtgs00c5xtevg/2W4A1500.JPG?dl=1", 146 1200, 147 ), 148 ) 149 150 assert.Equal(t, 151 []*DownloadedImageInfo{ 152 { 153 "/photographs/belize/01/kukumba-beach-1", 154 mustURL(t, "https://www.dropbox.com/s/6fmtgs00c5xtevg/2W4A1500.JPG?dl=1"), 155 1200, 156 "", 157 }, 158 }, 159 downloadedImageContainer.Images, 160 ) 161 }) 162 163 t.Run("AlternateExtension", func(t *testing.T) { 164 ctx, _ := DownloadedImageContext(ctx) 165 166 assert.Equal(t, 167 "/photographs/diagram.png", 168 DownloadedImage( 169 ctx, 170 "/photographs/diagram", 171 "https://www.dropbox.com/s/6fmtgs00c5xtevg/2W4A1500.png?dl=1", 172 1200, 173 ), 174 ) 175 }) 176 } 177 178 func mustURL(t *testing.T, s string) *url.URL { 179 t.Helper() 180 u, err := url.Parse(s) 181 assert.NoError(t, err) 182 return u 183 } 184 185 func TestFigure(t *testing.T) { 186 t.Run("SingleImage", func(t *testing.T) { 187 assert.Equal( 188 t, 189 strings.TrimSpace(` 190 <figure> 191 <img alt="alt" loading="lazy" src="src" srcset="src@2x 2x, src 1x"> 192 <figcaption>caption</figcaption> 193 </figure> 194 `), 195 string(Figure("caption", &HTMLImage{Src: "src", Alt: "alt"})), 196 ) 197 }) 198 199 t.Run("MultipleImages", func(t *testing.T) { 200 assert.Equal( 201 t, 202 strings.TrimSpace(` 203 <figure> 204 <img alt="alt0" loading="lazy" src="src0" srcset="src0@2x 2x, src0 1x"> 205 <img alt="alt1" loading="lazy" src="src1" srcset="src1@2x 2x, src1 1x"> 206 <img alt="alt2" loading="lazy" src="src2" srcset="src2@2x 2x, src2 1x"> 207 <figcaption>caption</figcaption> 208 </figure> 209 `), 210 string(Figure( 211 "caption", 212 &HTMLImage{Src: "src0", Alt: "alt0"}, 213 &HTMLImage{Src: "src1", Alt: "alt1"}, 214 &HTMLImage{Src: "src2", Alt: "alt2"}, 215 )), 216 ) 217 }) 218 t.Run("NoCaption", func(t *testing.T) { 219 assert.Equal( 220 t, 221 strings.TrimSpace(` 222 <figure> 223 <img loading="lazy" src="src" srcset="src@2x 2x, src 1x"> 224 </figure> 225 `), 226 string(Figure("", &HTMLImage{Src: "src"})), 227 ) 228 }) 229 } 230 231 func TestFormatTime(t *testing.T) { 232 assert.Equal(t, "July 3, 2016 12:34", FormatTime(testTime, "January 2, 2006 15:04")) 233 } 234 235 func TestFormatTimeRFC3339UTC(t *testing.T) { 236 assert.Equal(t, "2016-07-03T12:34:00Z", FormatTimeRFC3339UTC(testTime)) 237 } 238 239 func TestFormatTimeSimpleDate(t *testing.T) { 240 assert.Equal(t, "July 3, 2016", FormatTimeSimpleDate(testTime)) 241 } 242 243 func TestHTMLImageRender(t *testing.T) { 244 t.Run("Basic", func(t *testing.T) { 245 img := HTMLImage{Src: "src", Alt: "alt"} 246 assert.Equal( 247 t, 248 `<img alt="alt" loading="lazy" src="src" srcset="src@2x 2x, src 1x">`, 249 string(img.render()), 250 ) 251 }) 252 253 t.Run("NoSrcsetForSVG", func(t *testing.T) { 254 img := HTMLImage{Src: "src.svg", Alt: "alt"} 255 assert.Equal( 256 t, 257 `<img alt="alt" loading="lazy" src="src.svg">`, 258 string(img.render()), 259 ) 260 }) 261 262 t.Run("WithClass", func(t *testing.T) { 263 img := HTMLImage{Src: "src", Alt: "alt", Class: "class"} 264 assert.Equal( 265 t, 266 `<img alt="alt" class="class" loading="lazy" src="src" srcset="src@2x 2x, src 1x">`, 267 string(img.render()), 268 ) 269 }) 270 } 271 272 func TestHTMLRender(t *testing.T) { 273 t.Run("SingleElement", func(t *testing.T) { 274 assert.Equal( 275 t, 276 strings.TrimSpace(` 277 <img alt="alt" loading="lazy" src="src" srcset="src@2x 2x, src 1x"> 278 `), 279 string(HTMLRender( 280 &HTMLImage{Src: "src", Alt: "alt"}, 281 )), 282 ) 283 }) 284 285 t.Run("MultipleElements", func(t *testing.T) { 286 assert.Equal( 287 t, 288 strings.TrimSpace(` 289 <img alt="alt0" loading="lazy" src="src0" srcset="src0@2x 2x, src0 1x"> 290 <img alt="alt1" loading="lazy" src="src1" srcset="src1@2x 2x, src1 1x"> 291 <img alt="alt2" loading="lazy" src="src2" srcset="src2@2x 2x, src2 1x"> 292 `), 293 string(HTMLRender( 294 &HTMLImage{Src: "src0", Alt: "alt0"}, 295 &HTMLImage{Src: "src1", Alt: "alt1"}, 296 &HTMLImage{Src: "src2", Alt: "alt2"}, 297 )), 298 ) 299 }) 300 } 301 302 func TestHTMLSafePassThrough(t *testing.T) { 303 assert.Equal(t, `{{print "x"}}`, string(HTMLSafePassThrough(`{{print "x"}}`))) 304 } 305 306 func TestImgSrcAndAlt(t *testing.T) { 307 assert.Equal(t, HTMLImage{Src: "src", Alt: "alt"}, *ImgSrcAndAlt("src", "alt")) 308 } 309 310 func TestImgSrcAndAltAndClass(t *testing.T) { 311 assert.Equal( 312 t, 313 HTMLImage{Src: "src", Alt: "alt", Class: "class"}, 314 *ImgSrcAndAltAndClass("src", "alt", "class"), 315 ) 316 } 317 318 func TestMap(t *testing.T) { 319 m := Map(MapVal("New", 456)) 320 assert.Contains(t, m, "New") 321 } 322 323 func TestMapValAdd(t *testing.T) { 324 m := map[string]interface{}{ 325 "Preexisting": 123, 326 } 327 328 newM := MapValAdd(m, MapVal("New", 456)) 329 330 assert.Contains(t, newM, "Preexisting") 331 assert.Contains(t, newM, "New") 332 333 assert.Contains(t, m, "Preexisting") 334 assert.NotContains(t, m, "New") 335 } 336 337 func TestQueryEscape(t *testing.T) { 338 assert.Equal(t, "a%2Bb", QueryEscape("a+b")) 339 } 340 341 func TestRomanNumeral(t *testing.T) { 342 assert.Equal(t, "I", RomanNumeral(1)) 343 assert.Equal(t, "II", RomanNumeral(2)) 344 assert.Equal(t, "III", RomanNumeral(3)) 345 assert.Equal(t, "IV", RomanNumeral(4)) 346 assert.Equal(t, "V", RomanNumeral(5)) 347 assert.Equal(t, "VI", RomanNumeral(6)) 348 assert.Equal(t, "VII", RomanNumeral(7)) 349 assert.Equal(t, "VIII", RomanNumeral(8)) 350 assert.Equal(t, "IX", RomanNumeral(9)) 351 assert.Equal(t, "X", RomanNumeral(10)) 352 assert.Equal(t, "XI", RomanNumeral(11)) 353 assert.Equal(t, "XII", RomanNumeral(12)) 354 assert.Equal(t, "XIII", RomanNumeral(13)) 355 assert.Equal(t, "XIV", RomanNumeral(14)) 356 assert.Equal(t, "XV", RomanNumeral(15)) 357 assert.Equal(t, "XVI", RomanNumeral(16)) 358 assert.Equal(t, "XVII", RomanNumeral(17)) 359 assert.Equal(t, "XVIII", RomanNumeral(18)) 360 assert.Equal(t, "XIX", RomanNumeral(19)) 361 assert.Equal(t, "XX", RomanNumeral(20)) 362 assert.Equal(t, "XXI", RomanNumeral(21)) 363 assert.Equal(t, "XL", RomanNumeral(40)) 364 assert.Equal(t, "L", RomanNumeral(50)) 365 assert.Equal(t, "LX", RomanNumeral(60)) 366 assert.Equal(t, "LXI", RomanNumeral(61)) 367 assert.Equal(t, "XC", RomanNumeral(90)) 368 assert.Equal(t, "C", RomanNumeral(100)) 369 assert.Equal(t, "CD", RomanNumeral(400)) 370 assert.Equal(t, "D", RomanNumeral(500)) 371 assert.Equal(t, "CM", RomanNumeral(900)) 372 assert.Equal(t, "M", RomanNumeral(1000)) 373 assert.Equal(t, "MCMXCIX", RomanNumeral(1999)) 374 assert.Equal(t, "MMMCMXCIX", RomanNumeral(3999)) 375 376 // Out of range 377 assert.Equal(t, "0", RomanNumeral(0)) 378 assert.Equal(t, "4000", RomanNumeral(4000)) 379 } 380 381 func TestRoundToString(t *testing.T) { 382 assert.Equal(t, "1.2", RoundToString(1.234)) 383 assert.Equal(t, "1.0", RoundToString(1)) 384 } 385 386 func TestTimeIn(t *testing.T) { 387 tIn := TimeIn(testTime, "America/Los_Angeles") 388 assert.Equal(t, "America/Los_Angeles", tIn.Location().String()) 389 } 390 391 func TestTo2X(t *testing.T) { 392 assert.Equal(t, template.HTML("/path/image@2x.jpg"), To2X("/path/image.jpg")) 393 assert.Equal(t, template.HTML("/path/image@2x.png"), To2X("/path/image.png")) 394 assert.Equal(t, template.HTML("image@2x.jpg"), To2X("image.jpg")) 395 assert.Equal(t, template.HTML("image"), To2X("image")) 396 assert.Equal(t, template.HTML("photos/reddit/rd_xxx_01/11%20-%20t9kxD78@2x.jpg"), 397 To2X("photos/reddit/rd_xxx_01/11%20-%20t9kxD78.jpg")) 398 } 399 400 ////////////////////////////////////////////////////////////////////////////// 401 // 402 // 403 // 404 // Private 405 // 406 // 407 // 408 ////////////////////////////////////////////////////////////////////////////// 409 410 func mustParseDuration(s string) time.Duration { 411 d, err := time.ParseDuration(s) 412 if err != nil { 413 panic(err) 414 } 415 return d 416 }