package pages import ( "errors" "html/template" "net/http" "net/http/httptest" "testing" "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // view is the dot every test renders through: the shape the donors' view // structs have — chrome fields (here only a title) plus the page's payload // under .Data, which is what the shipped error page reads. type view struct { Title string Data any } // chromeMark is written by the layout before it reaches the content hole, so a // test can tell "nothing was written" from "half a page was written". const chromeMark = "CHROME-MARK" // boomer fails at execution and only there: html/template reports an error // returned by a method as a render error, which is the mid-render failure the // buffering exists for. Its message names something a viewer must never see. type boomer struct{} func (boomer) Boom() (string, error) { return "", errors.New("kaboom /etc/srht/config.ini") } // testFS is a service's template tree: a layout, a page, a partial, and a page // that fails halfway through rendering. func testFS() fstest.MapFS { return fstest.MapFS{ "templates/layout.html": &fstest.MapFile{Data: []byte( `
index {{template "_row" .Data}}
{{end}}`)}, "templates/_row.html": &fstest.MapFile{Data: []byte( `{{define "_row"}}{{.}}{{end}}`)}, "templates/boom.html": &fstest.MapFile{Data: []byte( `{{define "content"}}{{.Data.Boom}}
{{end}}`)}, } } func TestLoadDiscoversPagesPartialsAndTheShippedErrorPage(t *testing.T) { set, err := Load(testFS(), Options{}) require.NoError(t, err) // The pages, plus the error page this package ships. The layout and the // partial are not pages and must not be renderable by name. assert.ElementsMatch(t, []string{"index", "boom", ErrorPage}, keys(set)) } func TestLoadParsesEveryPartialIntoEveryPage(t *testing.T) { set, err := Load(testFS(), Options{}) require.NoError(t, err) // A partial is parsed into every set, not only into the page that invokes // it today — including the error page, which invokes none of them. for name, tmpl := range set { assert.NotNil(t, tmpl.Lookup("_row"), "local partial missing from %q", name) assert.NotNil(t, tmpl.Lookup(ErrorPartial), "error partial missing from %q", name) assert.NotNil(t, tmpl.Lookup("srht-nav"), "chrome partial missing from %q", name) } } func TestLoadRefusesAPageThatDefinesNoContent(t *testing.T) { fsys := testFS() // A page that renders nothing into the hole: executed, it would answer 200 // with the chrome around an empty document. fsys["templates/blank.html"] = &fstest.MapFile{Data: []byte(`forgot the define
`)} _, err := Load(fsys, Options{}) require.Error(t, err) assert.ErrorIs(t, err, ErrNoContent) assert.Contains(t, err.Error(), "blank.html") } func TestLoadRefusesATreeWithoutALayout(t *testing.T) { fsys := testFS() delete(fsys, "templates/layout.html") _, err := Load(fsys, Options{}) require.Error(t, err) assert.Contains(t, err.Error(), "no layout") } func TestLoadRefusesATreeWithNoPages(t *testing.T) { fsys := fstest.MapFS{ "templates/layout.html": &fstest.MapFile{Data: []byte(`{{template "content" .}}`)}, "templates/_row.html": &fstest.MapFile{Data: []byte(`{{define "_row"}}{{end}}`)}, } _, err := Load(fsys, Options{}) require.Error(t, err) assert.Contains(t, err.Error(), "no page templates") } func TestLoadMergesTheServiceFuncsOverChromes(t *testing.T) { fsys := testFS() fsys["templates/index.html"] = &fstest.MapFile{Data: []byte( `{{define "content"}}{{shortsha "0123456789"}}|{{shout "hi"}}{{end}}`)} set, err := Load(fsys, Options{Funcs: template.FuncMap{ "shout": func(s string) string { return s + "!" }, "shortsha": func(string) string { return "SHADOWED" }, }}) require.NoError(t, err) rec := httptest.NewRecorder() require.NoError(t, set.Render(rec, http.StatusOK, "index", view{})) assert.Contains(t, rec.Body.String(), "SHADOWED|hi!") } func TestRenderWritesTheStatusAndTheBufferedPage(t *testing.T) { set, err := Load(testFS(), Options{}) require.NoError(t, err) rec := httptest.NewRecorder() require.NoError(t, set.Render(rec, http.StatusCreated, "index", view{Title: "T", Data: "payload"})) assert.Equal(t, http.StatusCreated, rec.Code) assert.Equal(t, "text/html; charset=utf-8", rec.Header().Get("Content-Type")) body := rec.Body.String() assert.Contains(t, body, "