socle

test: testmark HTML snapshots of the browse pages

commit 619b45eb155c50a255def20b05c701cff3c16faa · parent 44017bb364 · browse this tree
Ximianth, 2026-10-26 20:06:41 UTC

Ten cases in testdata/pages.md over the hermetic fixture: index, home, tree
listings, file views (branch- and sha-addressed), log, and the two error
shapes.  Regenerable with -testmark.regen; watched failing against a
perturbed template before trusting.

Writing the first snapshots surfaced a real bug, fixed here: path.Dir's
"." result for top-level entries leaked into parent links and crumbs
(hrefs like /tree/main/./); parentDir now returns "" for the root.
diff --git a/pages.go b/pages.go
index d5d8d68..d1acc8e 100644
--- a/pages.go
+++ b/pages.go
@@ -281,7 +281,7 @@ func (s *server) serveTreeListing(w http.ResponseWriter, rp *repo, ref, sha, pth
 		d.Title = rp.name
 	}
 	if pth != "" {
-		d.Rows = append(d.Rows, treeRow{Name: "..", Href: treeHref(rp.name, ref, path.Dir(strings.TrimSuffix(pth, "/"))) + "/"})
+		d.Rows = append(d.Rows, treeRow{Name: "..", Href: treeHref(rp.name, ref, parentDir(pth)) + "/"})
 	}
 	for _, e := range entries {
 		full := e.name
@@ -319,6 +319,14 @@ func treeCrumbs(name, ref, pth string, more ...crumbLink) []display.Crumb {
 	return repoCrumbs(name, append(cs, more...)...)
 }
 
+// parentDir is path.Dir for tree paths: "" (the root), not ".", for a top-level entry.
+func parentDir(pth string) string {
+	if d := path.Dir(pth); d != "." {
+		return d
+	}
+	return ""
+}
+
 func pageTitle(name, ref, pth string) string {
 	t := name
 	if pth != "" {
@@ -360,7 +368,7 @@ func (s *server) serveFileView(w http.ResponseWriter, rp *repo, ref, sha, pth st
 	raw := rawHref(rp.name, ref, pth)
 	pd := display.PageData{
 		Title:   pageTitle(rp.name, ref, pth),
-		Crumbs:  treeCrumbs(rp.name, ref, path.Dir(pth), crumbLink{Name: name}),
+		Crumbs:  treeCrumbs(rp.name, ref, parentDir(pth), crumbLink{Name: name}),
 		RawHref: raw,
 	}
 	if isImagePath(pth) {
diff --git a/pages_test.go b/pages_test.go
new file mode 100644
index 0000000..53e8116
--- /dev/null
+++ b/pages_test.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"testing"
+
+	"github.com/warpfork/go-testmark"
+)
+
+// TestPageSnapshots drives the browse pages through the fixture cases in testdata/pages.md.
+// Each case is a pair of hunks: <case>/url (the GET target) and <case>/response
+// (status line, the interesting headers, a blank line, then the body).
+// Run with -testmark.regen to regenerate the response hunks and review via git diff.
+//
+// Determinism rests on the hermetic fixture repo (pinned shas), the pinned theme seed,
+// and httptest's fixed request host.  The fixture shas appear verbatim in the snapshots;
+// if they drift, gitfixture_test.go is the place to look.
+func TestPageSnapshots(t *testing.T) {
+	doc, err := testmark.ReadFile("testdata/pages.md")
+	if err != nil {
+		t.Fatalf("reading fixtures: %v", err)
+	}
+	doc.BuildDirIndex()
+	s := fixtureServer(t)
+	var pa testmark.PatchAccumulator
+	for _, dirent := range doc.DirEnt.ChildrenList {
+		t.Run(dirent.Name, func(t *testing.T) {
+			urlEnt := dirent.Children["url"]
+			resEnt := dirent.Children["response"]
+			if urlEnt == nil || urlEnt.Hunk == nil || resEnt == nil || resEnt.Hunk == nil {
+				t.Fatalf("fixture %q needs both a url and a response hunk", dirent.Name)
+			}
+			target := strings.TrimSpace(string(urlEnt.Hunk.Body))
+			got := describeResponse(s, target)
+			if *testmark.Regen {
+				pa.AppendPatchIfBodyDiffers(*resEnt.Hunk, []byte(got))
+			} else if got != string(resEnt.Hunk.Body) {
+				t.Errorf("response mismatch for %s:\n--- got:\n%s--- want:\n%s", target, got, resEnt.Hunk.Body)
+			}
+		})
+	}
+	if *testmark.Regen {
+		if err := pa.WriteFileWithPatches(doc, "testdata/pages.md"); err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+// describeResponse renders one GET as the stable text the fixtures assert on:
+// the status, every header except the content-length (dull) and date-ish ones, then the body.
+func describeResponse(s *server, target string) string {
+	rec := do(s, target, nil, nil)
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "status: %d\n", rec.Code)
+	names := make([]string, 0, len(rec.Header()))
+	for name := range rec.Header() {
+		if name != "Content-Length" {
+			names = append(names, name)
+		}
+	}
+	sort.Strings(names)
+	for _, name := range names {
+		fmt.Fprintf(&sb, "%s: %s\n", name, strings.Join(rec.Header()[name], ", "))
+	}
+	sb.WriteString("\n")
+	sb.Write(rec.Body.Bytes())
+	return sb.String()
+}
diff --git a/testdata/pages.md b/testdata/pages.md
new file mode 100644
index 0000000..6b6412d
--- /dev/null
+++ b/testdata/pages.md
@@ -0,0 +1,357 @@
+pages.md: browse-page snapshot fixtures
+=======================================
+
+HTML snapshots of socle's browse pages, served over the hermetic fixture repo
+(see gitfixture_test.go; its pinned shas appear verbatim below).
+Each case is a `url` hunk (the GET target) and a `response` hunk
+(status, headers minus content-length, blank line, body).
+Regenerate with `go test -run TestPageSnapshots . -testmark.regen` and review the diff.
+
+
+index
+-----
+
+The repo index: names, descriptions, newest commit subjects.
+
+[testmark]:# (index/url)
+```text
+/
+```
+
+[testmark]:# (index/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>socle</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+socle<span class="theme">@test</span>
+
+</nav>
+
+<article><h1>repositories</h1>
+<table class="log">
+<tbody>
+<tr><td><a href="/fixturama/">fixturama</a></td><td>the fixture</td><td class="date">add docs and src</td></tr>
+</tbody>
+</table>
+</article>
+
+</body>
+</html>
+```
+
+
+repo home
+---------
+
+Clone URL, branches and tags, and the README rendered at HEAD.
+
+[testmark]:# (repo-home/url)
+```text
+/fixturama/
+```
+
+[testmark]:# (repo-home/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / fixturama
+
+</nav>
+
+<article><p>the fixture</p>
+<p class="clone">clone: <code>git clone http://socle.example/fixturama</code></p>
+<p><a href="/fixturama/tree/main/">files</a> · <a href="/fixturama/log/main">log</a>
+· branches: <a href="/fixturama/tree/dev/">dev</a>, <a href="/fixturama/tree/main/">main</a>
+· tags: <a href="/fixturama/tree/v1.0/">v1.0</a></p>
+<h1 id="fixturama">Fixturama<a href="#fixturama" class="anchor">🔗</a></h1>
+<p>A repo that exists to be looked at.</p>
+</article>
+
+</body>
+</html>
+```
+
+
+tree listings
+-------------
+
+The root tree, and a subdirectory (note the escaped space in the file link).
+
+[testmark]:# (tree-root/url)
+```text
+/fixturama/tree/main/
+```
+
+[testmark]:# (tree-root/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama @ main</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / <a href="/fixturama/">fixturama</a> / main
+
+</nav>
+
+<article><h1>fixturama</h1>
+<table class="tree">
+<tbody>
+<tr><td><a href="/fixturama/tree/main/README.md">README.md</a></td><td class="size">49</td></tr>
+<tr><td><a href="/fixturama/tree/main/docs/">docs/</a></td><td class="size"></td></tr>
+<tr><td><a href="/fixturama/tree/main/src/">src/</a></td><td class="size"></td></tr>
+</tbody>
+</table>
+</article>
+
+</body>
+</html>
+```
+
+[testmark]:# (tree-docs/url)
+```text
+/fixturama/tree/main/docs/
+```
+
+[testmark]:# (tree-docs/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama/docs @ main</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / <a href="/fixturama/">fixturama</a> / <a href="/fixturama/tree/main/">main</a> / docs
+
+</nav>
+
+<article><h1>docs</h1>
+<table class="tree">
+<tbody>
+<tr><td><a href="/fixturama/tree/main/">..</a></td><td class="size"></td></tr>
+<tr><td><a href="/fixturama/tree/main/docs/notes.md">notes.md</a></td><td class="size">17</td></tr>
+<tr><td><a href="/fixturama/tree/main/docs/with%20space.txt">with space.txt</a></td><td class="size">25</td></tr>
+</tbody>
+</table>
+</article>
+
+</body>
+</html>
+```
+
+
+file views
+----------
+
+Markdown renders; plain text shows preformatted.
+The sha-addressed variant also demonstrates the immutable cache header.
+
+[testmark]:# (file-markdown/url)
+```text
+/fixturama/tree/main/README.md
+```
+
+[testmark]:# (file-markdown/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama/README.md @ main</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / <a href="/fixturama/">fixturama</a> / <a href="/fixturama/tree/main/">main</a> / README.md
+<a class="raw" href="/fixturama/raw/main/README.md">raw</a>
+</nav>
+
+<article><h1>README.md</h1>
+<h1 id="fixturama">Fixturama<a href="#fixturama" class="anchor">🔗</a></h1>
+<p>A repo that exists to be looked at.</p>
+</article>
+
+</body>
+</html>
+```
+
+[testmark]:# (file-text/url)
+```text
+/fixturama/tree/main/docs/with%20space.txt
+```
+
+[testmark]:# (file-text/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama/docs/with space.txt @ main</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / <a href="/fixturama/">fixturama</a> / <a href="/fixturama/tree/main/">main</a> / <a href="/fixturama/tree/main/docs/">docs</a> / with space.txt
+<a class="raw" href="/fixturama/raw/main/docs/with%20space.txt">raw</a>
+</nav>
+
+<article><h1>with space.txt</h1>
+<pre class="file">even this filename works
+</pre></article>
+
+</body>
+</html>
+```
+
+[testmark]:# (file-at-sha/url)
+```text
+/fixturama/tree/9db23702e9ae3e8317b5dba562b0f3bd1cb25e7f/README.md
+```
+
+[testmark]:# (file-at-sha/response)
+```text
+status: 200
+Cache-Control: public, max-age=31536000, immutable
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama/README.md @ 9db23702e9</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / <a href="/fixturama/">fixturama</a> / <a href="/fixturama/tree/9db23702e9ae3e8317b5dba562b0f3bd1cb25e7f/">9db23702e9ae3e8317b5dba562b0f3bd1cb25e7f</a> / README.md
+<a class="raw" href="/fixturama/raw/9db23702e9ae3e8317b5dba562b0f3bd1cb25e7f/README.md">raw</a>
+</nav>
+
+<article><h1>README.md</h1>
+<h1 id="fixturama">Fixturama<a href="#fixturama" class="anchor">🔗</a></h1>
+<p>A repo that exists to be looked at.</p>
+</article>
+
+</body>
+</html>
+```
+
+
+log
+---
+
+[testmark]:# (log-main/url)
+```text
+/fixturama/log/main
+```
+
+[testmark]:# (log-main/response)
+```text
+status: 200
+Content-Type: text/html; charset=utf-8
+
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>fixturama log @ main</title>
+<link rel="stylesheet" href="/-/style.css">
+</head>
+<body>
+<nav>
+<a href="/">socle</a><span class="theme">@test</span> / <a href="/fixturama/">fixturama</a> / log / main
+
+</nav>
+
+<article><h1>log: main</h1>
+<table class="log">
+<thead><tr><th>commit</th><th>date</th><th>author</th><th>subject</th></tr></thead>
+<tbody>
+<tr><td class="hash"><a href="/fixturama/tree/9db23702e9ae3e8317b5dba562b0f3bd1cb25e7f/">9db23702e9</a></td><td class="date">2026-01-02</td><td>Ada Fixture</td><td>add docs and src</td></tr>
+<tr><td class="hash"><a href="/fixturama/tree/64ac67a35d36079e167cb8f929fd57f859c8b5d1/">64ac67a35d</a></td><td class="date">2026-01-02</td><td>Ada Fixture</td><td>inaugurate with a readme</td></tr>
+</tbody>
+</table>
+</article>
+
+</body>
+</html>
+```
+
+
+error pages
+-----------
+
+A malformed ref is the client's fault; an unknown repo simply isn't there.
+
+[testmark]:# (err-bad-ref/url)
+```text
+/fixturama/tree/main..dev/
+```
+
+[testmark]:# (err-bad-ref/response)
+```text
+status: 400
+Content-Type: text/plain; charset=utf-8
+X-Content-Type-Options: nosniff
+
+invalid: ref "main..dev" contains ".."
+```
+
+[testmark]:# (err-unknown-repo/url)
+```text
+/nosuch/
+```
+
+[testmark]:# (err-unknown-repo/response)
+```text
+status: 404
+Content-Type: text/plain; charset=utf-8
+X-Content-Type-Options: nosniff
+
+404 page not found
+```