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  }