Go · 5829 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package render
4
5 import (
6 "bytes"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 "testing/fstest"
11 )
12
13 // Each test builds a tiny in-memory template tree to exercise one
14 // invariant of New(). Keep these focused; broader render flows are
15 // covered by handler-level tests.
16
17 func TestNew_RegistersRootPages(t *testing.T) {
18 t.Parallel()
19 fsys := fstest.MapFS{
20 "_layout.html": &fstest.MapFile{Data: []byte(`{{ define "layout" }}<html>{{ template "body" . }}</html>{{ end }}`)},
21 "home.html": &fstest.MapFile{Data: []byte(`{{ define "body" }}home page{{ end }}`)},
22 }
23 r, err := New(fsys, Options{})
24 if err != nil {
25 t.Fatalf("New: %v", err)
26 }
27 var buf bytes.Buffer
28 if err := r.Render(&buf, "home", nil); err != nil {
29 t.Fatalf("render: %v", err)
30 }
31 if !strings.Contains(buf.String(), "home page") {
32 t.Errorf("rendered output missing body: %q", buf.String())
33 }
34 }
35
36 func TestNew_RegistersSubdirPages(t *testing.T) {
37 t.Parallel()
38 fsys := fstest.MapFS{
39 "_layout.html": &fstest.MapFile{Data: []byte(`{{ define "layout" }}<html>{{ template "body" . }}</html>{{ end }}`)},
40 "errors/404.html": &fstest.MapFile{Data: []byte(`{{ define "body" }}not found{{ end }}`)},
41 }
42 r, err := New(fsys, Options{})
43 if err != nil {
44 t.Fatalf("New: %v", err)
45 }
46 var buf bytes.Buffer
47 if err := r.Render(&buf, "errors/404", nil); err != nil {
48 t.Fatalf("render: %v", err)
49 }
50 if !strings.Contains(buf.String(), "not found") {
51 t.Errorf("rendered output missing body: %q", buf.String())
52 }
53 }
54
55 func TestRenderFragmentExecutesPageWithoutLayout(t *testing.T) {
56 t.Parallel()
57 fsys := fstest.MapFS{
58 "_layout.html": &fstest.MapFile{Data: []byte(
59 `{{ define "layout" }}<html>{{ template "page" . }}</html>{{ end }}`,
60 )},
61 "fragment.html": &fstest.MapFile{Data: []byte(
62 `{{ define "page" }}fragment only{{ end }}`,
63 )},
64 }
65 r, err := New(fsys, Options{})
66 if err != nil {
67 t.Fatalf("New: %v", err)
68 }
69 var buf bytes.Buffer
70 if err := r.RenderFragment(&buf, "fragment", nil); err != nil {
71 t.Fatalf("render fragment: %v", err)
72 }
73 if got := buf.String(); got != "fragment only" {
74 t.Fatalf("RenderFragment body = %q, want fragment only", got)
75 }
76 }
77
78 // Regression test for the inbound deferral from S30 dogfood: a partial
79 // at `profile/_tabs.html` that defines `{{ define "tabs" }}` was
80 // silently registered as an unparsed page. A page that called
81 // `{{ template "tabs" . }}` then rendered blank.
82 func TestNew_LoadsSubdirPartials(t *testing.T) {
83 t.Parallel()
84 fsys := fstest.MapFS{
85 "_layout.html": &fstest.MapFile{Data: []byte(
86 `{{ define "layout" }}<html>{{ template "body" . }}</html>{{ end }}`,
87 )},
88 "profile/_tabs.html": &fstest.MapFile{Data: []byte(
89 `{{ define "tabs" }}TAB CONTENT{{ end }}`,
90 )},
91 "profile.html": &fstest.MapFile{Data: []byte(
92 `{{ define "body" }}{{ template "tabs" . }}{{ end }}`,
93 )},
94 }
95 r, err := New(fsys, Options{})
96 if err != nil {
97 t.Fatalf("New: %v", err)
98 }
99 var buf bytes.Buffer
100 if err := r.Render(&buf, "profile", nil); err != nil {
101 t.Fatalf("render: %v", err)
102 }
103 if !strings.Contains(buf.String(), "TAB CONTENT") {
104 t.Errorf("subdir partial not loaded — body was %q", buf.String())
105 }
106 }
107
108 // A page that references a template name nothing defines should fail
109 // LOUDLY at New() time, not silently render blank at exec time.
110 func TestNew_FailsOnUndefinedTemplateRef(t *testing.T) {
111 t.Parallel()
112 fsys := fstest.MapFS{
113 "_layout.html": &fstest.MapFile{Data: []byte(
114 `{{ define "layout" }}<html>{{ template "body" . }}</html>{{ end }}`,
115 )},
116 "broken.html": &fstest.MapFile{Data: []byte(
117 `{{ define "body" }}{{ template "does-not-exist" . }}{{ end }}`,
118 )},
119 }
120 _, err := New(fsys, Options{})
121 if err == nil {
122 t.Fatal("New: expected error for undefined template ref, got nil")
123 }
124 if !strings.Contains(err.Error(), "does-not-exist") {
125 t.Errorf("error should name the missing template; got %v", err)
126 }
127 if !strings.Contains(err.Error(), "broken") {
128 t.Errorf("error should name the offending page; got %v", err)
129 }
130 }
131
132 // Sanity: refs into partials still resolve (not flagged as undefined).
133 func TestNew_AcceptsRefsResolvedByPartials(t *testing.T) {
134 t.Parallel()
135 fsys := fstest.MapFS{
136 "_layout.html": &fstest.MapFile{Data: []byte(
137 `{{ define "layout" }}{{ template "header" . }}{{ template "body" . }}{{ end }}`,
138 )},
139 "_header.html": &fstest.MapFile{Data: []byte(
140 `{{ define "header" }}HDR{{ end }}`,
141 )},
142 "page.html": &fstest.MapFile{Data: []byte(
143 `{{ define "body" }}body{{ end }}`,
144 )},
145 }
146 r, err := New(fsys, Options{})
147 if err != nil {
148 t.Fatalf("New: %v", err)
149 }
150 var buf bytes.Buffer
151 if err := r.Render(&buf, "page", nil); err != nil {
152 t.Fatalf("render: %v", err)
153 }
154 got := buf.String()
155 if !strings.Contains(got, "HDR") || !strings.Contains(got, "body") {
156 t.Errorf("missing partial or page output: %q", got)
157 }
158 }
159
160 // RenderPage preserves any Viewer / CSRFToken the handler set itself,
161 // rather than overwriting them. The auto-inject is for handlers that
162 // hand RenderPage an empty map; explicit handlers stay in control.
163 func TestRenderPage_PreservesExplicitViewer(t *testing.T) {
164 t.Parallel()
165 fsys := fstest.MapFS{
166 "_layout.html": &fstest.MapFile{Data: []byte(
167 `{{ define "layout" }}{{ template "body" . }}{{ end }}`,
168 )},
169 "page.html": &fstest.MapFile{Data: []byte(
170 `{{ define "body" }}viewer={{ .Viewer }}{{ end }}`,
171 )},
172 }
173 r, err := New(fsys, Options{})
174 if err != nil {
175 t.Fatalf("New: %v", err)
176 }
177 req := httptest.NewRequest("GET", "/", nil)
178 rw := httptest.NewRecorder()
179 if err := r.RenderPage(rw, req, "page", map[string]any{
180 "Viewer": "explicit",
181 }); err != nil {
182 t.Fatalf("render: %v", err)
183 }
184 if !strings.Contains(rw.Body.String(), "viewer=explicit") {
185 t.Errorf("explicit Viewer was overwritten; body=%q", rw.Body.String())
186 }
187 }
188