internal/web/web_test.go
118 lines · 4408 bytes
1package web
2
3import (
4 "io/fs"
5 "regexp"
6 "strings"
7 "testing"
8)
9
10// Every page template parses with the layout at start-up, so a broken
11// template fails the process rather than the first visit to its page.
12// Package init has already done the work; this asserts it covered every
13// file (#116).
14func TestEveryPageTemplateParses(t *testing.T) {
15 names := Pages()
16 if len(names) < 30 {
17 t.Fatalf("only %d page templates parsed: %v", len(names), names)
18 }
19 for _, want := range []string{"tree.html", "blob.html", "mr.html", "issue.html", "dashboard.html", "registered.html"} {
20 if _, ok := pages[want]; !ok {
21 t.Errorf("%s not parsed", want)
22 }
23 }
24}
25
26// A th with no rule of its own falls back to the browser's centred default
27// (#151). The classes that do left-align one are read out of the stylesheet
28// rather than listed here, so a new rule does not need a test change.
29func TestHeaderRowsAreLeftAligned(t *testing.T) {
30 aligned := map[string]bool{}
31 for _, rule := range strings.Split(string(StyleCSS), "}") {
32 sel, decls, ok := strings.Cut(rule, "{")
33 if !ok || !strings.Contains(decls, "text-align: left") || !strings.Contains(sel, "th") {
34 continue
35 }
36 for _, class := range regexp.MustCompile(`\.([a-z-]+)`).FindAllStringSubmatch(sel, -1) {
37 aligned[class[1]] = true
38 }
39 }
40 if len(aligned) == 0 {
41 t.Fatal("no left-aligning th rule found in the stylesheet")
42 }
43
44 classes := regexp.MustCompile(`class="([^"]*)"`)
45 entries, err := fs.ReadDir(templateFS, "templates")
46 if err != nil {
47 t.Fatal(err)
48 }
49 for _, e := range entries {
50 b, err := templateFS.ReadFile("templates/" + e.Name())
51 if err != nil {
52 t.Fatal(err)
53 }
54 // The table and row tags a header sits under, plus the header row
55 // itself: any one of them may carry the class.
56 for _, head := range strings.Split(string(b), "<thead>")[1:] {
57 open, _, _ := strings.Cut(head, "</tr>")
58 before := strings.Split(string(b), "<thead>"+head)[0]
59 if i := strings.LastIndex(before, "<table"); i >= 0 {
60 open += before[i:]
61 }
62 hit := false
63 for _, m := range classes.FindAllStringSubmatch(open, -1) {
64 for _, c := range strings.Fields(m[1]) {
65 hit = hit || aligned[c]
66 }
67 }
68 if !hit {
69 t.Errorf("%s: header row is not left-aligned by any rule: %.60s", e.Name(), open)
70 }
71 }
72 }
73}
74
75// when names the zone rather than leaving an absolute time ambiguous, and
76// passes an unparseable value through unchanged (#182).
77func TestWhenNamesTheZone(t *testing.T) {
78 got := funcs["when"].(func(string) string)("2026-09-12T02:18:07.123Z")
79 if got != "2026-09-12 02:18 UTC" {
80 t.Fatalf("when: %q", got)
81 }
82 if got := funcs["when"].(func(string) string)("not a time"); got != "not a time" {
83 t.Fatalf("passthrough: %q", got)
84 }
85}
86
87// TestMainWidthClass renders every page against an empty struct and
88// checks main carries exactly one width class, and that the pages the
89// spec calls wide or bounded say so.
90func TestMainWidthClass(t *testing.T) {
91 wide := map[string]bool{"tree.html": true, "blob.html": true, "blame.html": true, "log.html": true, "commit.html": true, "compare.html": true, "builds.html": true, "build.html": true, "search.html": true, "globalsearch.html": true}
92 bounded := map[string]bool{"landing.html": true, "login.html": true, "register.html": true, "registered.html": true, "new.html": true, "issuenew.html": true, "mrnew.html": true, "settings.html": true, "account.html": true, "admin.html": true, "edit.html": true, "snippetnew.html": true, "privacy.html": true, "404.html": true}
93 for _, name := range Pages() {
94 src, err := TemplateSource(name)
95 if err != nil {
96 t.Fatal(err)
97 }
98 has := strings.Contains(src, `{{define "width"}}`)
99 switch {
100 case wide[name] && !strings.Contains(src, `{{define "width"}}wide{{end}}`):
101 t.Errorf("%s: want width wide", name)
102 case bounded[name] && !strings.Contains(src, `{{define "width"}}bounded{{end}}`):
103 t.Errorf("%s: want width bounded", name)
104 case !wide[name] && !bounded[name] && has:
105 t.Errorf("%s: defines a width but the spec calls it reading", name)
106 }
107 }
108 layout, err := TemplateSource("layout.html")
109 if err != nil {
110 t.Fatal(err)
111 }
112 if !strings.Contains(layout, `<main id="content" class="content {{template "width" .}}">`) {
113 t.Error("layout.html: main does not carry the width block")
114 }
115 if !strings.Contains(layout, `{{define "width"}}reading{{end}}`) {
116 t.Error("layout.html: no default width")
117 }
118}