Show each page's own title in the browser tab (closes #117)
check / check (push) Successful in 3m12s
check / check (push) Successful in 3m12s
parsePageTemplate parsed the page file before htmlheader.html. A later
definition of a template name replaces an earlier one (an empty one
never does), so htmlheader.html's {{block "title"}} replaced every
page's {{define "title"}} and every tab read "Webhooker". The page file
is now parsed last, into a set named after it, so it stays the entry
point.
A new test renders every page template and checks its title; the
"Webhooker" fallback is checked on htmlheader.html alone, since every
page declares a title.
Model: opus-5-5
This commit is contained in:
@@ -110,22 +110,25 @@ type Handlers struct {
|
|||||||
// parsePageTemplate parses a page-specific template set from the
|
// parsePageTemplate parses a page-specific template set from the
|
||||||
// embedded FS. Each page template is combined with the shared
|
// embedded FS. Each page template is combined with the shared
|
||||||
// base, htmlheader, navbar and notice templates, and with any further
|
// base, htmlheader, navbar and notice templates, and with any further
|
||||||
// files the page includes. The page file must be listed first so that
|
// files the page includes. The set is named after the page file, so
|
||||||
// its root action ({{template "base" .}}) becomes the template set's
|
// the page's root action ({{template "base" .}}) is its entry point.
|
||||||
// entry point.
|
//
|
||||||
|
// The page file is parsed last because a later definition of a name
|
||||||
|
// replaces an earlier one: the page's {{define "title"}} must replace
|
||||||
|
// the {{block "title"}} fallback in htmlheader.html.
|
||||||
func parsePageTemplate(
|
func parsePageTemplate(
|
||||||
pageFile string, included ...string,
|
pageFile string, included ...string,
|
||||||
) *template.Template {
|
) *template.Template {
|
||||||
files := append([]string{
|
files := append([]string{
|
||||||
pageFile,
|
|
||||||
"base.html",
|
"base.html",
|
||||||
"htmlheader.html",
|
"htmlheader.html",
|
||||||
"navbar.html",
|
"navbar.html",
|
||||||
"notice.html",
|
"notice.html",
|
||||||
}, included...)
|
}, included...)
|
||||||
|
files = append(files, pageFile)
|
||||||
|
|
||||||
return template.Must(
|
return template.Must(
|
||||||
template.ParseFS(templates.Templates, files...),
|
template.New(pageFile).ParseFS(templates.Templates, files...),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
|
"sneak.berlin/go/webhooker/internal/handlers"
|
||||||
|
"sneak.berlin/go/webhooker/internal/session"
|
||||||
|
"sneak.berlin/go/webhooker/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestEveryPageRendersItsOwnTitle renders each page template and checks
|
||||||
|
// the browser tab title is the one the page declares, not the
|
||||||
|
// "Webhooker" fallback in htmlheader.html. A page that fails to render
|
||||||
|
// shows the error page's title instead, and fails here too.
|
||||||
|
func TestEveryPageRendersItsOwnTitle(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var h *handlers.Handlers
|
||||||
|
|
||||||
|
var sess *session.Session
|
||||||
|
|
||||||
|
app := newTestApp(t, &h, &sess)
|
||||||
|
app.RequireStart()
|
||||||
|
|
||||||
|
t.Cleanup(app.RequireStop)
|
||||||
|
|
||||||
|
// A pointer, as in the handlers: some pages call
|
||||||
|
// Webhook.RetentionLabel, a pointer method.
|
||||||
|
webhook := &database.Webhook{Name: "orders", RetentionDays: 14}
|
||||||
|
webhook.ID = testWebhookID
|
||||||
|
|
||||||
|
pages := []struct {
|
||||||
|
page string
|
||||||
|
data map[string]any
|
||||||
|
title string
|
||||||
|
}{
|
||||||
|
{"login.html", map[string]any{}, "Login - Webhooker"},
|
||||||
|
{"profile.html", map[string]any{}, "Profile - Webhooker"},
|
||||||
|
{"settings.html", map[string]any{}, "Settings - Webhooker"},
|
||||||
|
{"sources_list.html", map[string]any{}, "Webhooks - Webhooker"},
|
||||||
|
{"sources_new.html", map[string]any{}, "New Webhook - Webhooker"},
|
||||||
|
{
|
||||||
|
"source_detail.html",
|
||||||
|
map[string]any{dataKeyWebhook: webhook},
|
||||||
|
"orders - Webhooker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source_edit.html",
|
||||||
|
map[string]any{dataKeyWebhook: webhook},
|
||||||
|
"Edit orders - Webhooker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source_logs.html",
|
||||||
|
map[string]any{dataKeyWebhook: webhook, "TotalEvents": int64(0)},
|
||||||
|
"Full Event Log - orders - Webhooker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event_detail.html",
|
||||||
|
map[string]any{dataKeyWebhook: webhook},
|
||||||
|
"Event - orders - Webhooker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target_edit.html",
|
||||||
|
map[string]any{
|
||||||
|
dataKeyWebhook: webhook,
|
||||||
|
"Target": map[string]any{"Name": "alerts", "Type": "slack"},
|
||||||
|
},
|
||||||
|
"Edit alerts - Webhooker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"error.html",
|
||||||
|
map[string]any{"StatusText": http.StatusText(http.StatusNotFound)},
|
||||||
|
"Not Found - Webhooker",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range pages {
|
||||||
|
body := renderPage(t, h, sess, p.page, p.data)
|
||||||
|
|
||||||
|
_, afterOpen, _ := strings.Cut(body, "<title>")
|
||||||
|
title, _, _ := strings.Cut(afterOpen, "</title>")
|
||||||
|
|
||||||
|
assert.Equal(t, p.title, title, p.page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTitleFallbackIsWebhooker checks the title htmlheader.html gives a
|
||||||
|
// page that declares none. Every page declares one, so it is checked on
|
||||||
|
// htmlheader.html alone.
|
||||||
|
func TestTitleFallbackIsWebhooker(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
header := template.Must(
|
||||||
|
template.ParseFS(templates.Templates, "htmlheader.html"),
|
||||||
|
)
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
|
||||||
|
require.NoError(t, header.ExecuteTemplate(&buf, "htmlheader", nil))
|
||||||
|
assert.Contains(t, buf.String(), "<title>Webhooker</title>")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user