Download
119 lines 4.2 KiBGoModified 2026-10-18 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
package render_test

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestReadmePrecedenceAndTruncation(t *testing.T) {
	e := newEnv(t, "proj", "listing_truncate: 5\n")
	e.write("both/README", "plain readme\n")
	e.write("both/README.md", "# md readme\n")
	e.write("txt/readme.txt", "text readme <b>\n")
	e.write("none/file", "x\n")
	for i := range 8 {
		e.write(fmt.Sprintf("many/f%02d.txt", i), "x\n")
		e.write(fmt.Sprintf("bare/f%02d.txt", i), "x\n")
	}
	e.write("many/README.md", "# many\n")
	e.commit("readmes")
	e.push(false)
	e.render()
	mustContain(t, "both", e.read(e.page("both")), `<h1 id="md-readme">md readme</h1>`)
	mustNotContain(t, "both", e.read(e.page("both")), "plain readme</pre>")
	mustContain(t, "txt", e.read(e.page("txt")), `<pre class="plain">text readme &lt;b&gt;`)
	mustNotContain(t, "none", e.read(e.page("none")), `class="readme"`)
	// With a README the listing is a short table of contents; without one
	// every entry is shown.
	many := e.read(e.page("many"))
	mustContain(t, "many", many, `class="more-row"`, "4 more…", `class="file extra"`, "f07.txt", `id="many"`)
	bare := e.read(e.page("bare"))
	mustContain(t, "bare", bare, "f07.txt")
	mustNotContain(t, "bare", bare, `class="more-row"`, " extra")
}

func TestSymlinkAndSubmodule(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.write("target.txt", "t\n")
	if err := os.Symlink("target.txt", filepath.Join(e.work, "link")); err != nil {
		t.Skip("symlinks unsupported")
	}
	e.commit("symlink")
	// Fake a gitlink without a real submodule.
	e.git(e.work, "update-index", "--add", "--cacheinfo", "160000,"+strings.Repeat("a", 40)+",sub")
	e.git(e.work, "commit", "-q", "-m", "gitlink")
	e.push(false)
	e.render()
	root := e.read(e.page(""))
	mustContain(t, "root", root, "link", "→ target.txt", "sub", "@ aaaaaaa")
	e.mustNotExist(e.page("link"), e.raw("link"), e.page("sub"))
}

func TestSymlinkLinksToTarget(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.write("target.txt", "t\n")
	e.write("docs/a.md", "a\n")
	links := map[string]string{
		"top":           "target.txt",
		"docs/up":       "../target.txt",
		"docs/chain":    "../top",
		"docs/folder":   "../docs",
		"docs/via":      "../d/a.md", // through a symlinked folder
		"d":             "docs",
		"docs/dangling": "missing.txt",
		"docs/escape":   "../../outside",
		"docs/abs":      "/etc/passwd",
		"loop1":         "loop2",
		"loop2":         "loop1",
		"docs/later":    "../new.txt",
	}
	for l, target := range links {
		if err := os.Symlink(target, filepath.Join(e.work, filepath.FromSlash(l))); err != nil {
			t.Skip("symlinks unsupported")
		}
	}
	e.commit("symlinks")
	e.push(false)
	e.render()
	docs := e.read(e.page("docs"))
	mustContain(t, "docs listing", docs,
		`<a href="/code/proj/target.txt">up</a>`,
		`<a href="/code/proj/target.txt">chain</a>`,
		`<a href="/code/proj/docs">folder</a>`,
		`<a href="/code/proj/docs/a.md">via</a>`,
		"→ missing.txt", "→ ../../outside", "→ /etc/passwd")
	for _, name := range []string{"dangling", "escape", "abs", "later"} {
		if strings.Contains(docs, `">`+name+`</a>`) {
			t.Errorf("docs listing links %s", name)
		}
	}
	mustContain(t, "root listing", e.read(e.page("")),
		`<a href="/code/proj/target.txt">top</a>`, `<a href="/code/proj/docs">d</a>`)
	drawer := e.read(e.drawer())
	mustContain(t, "drawer", drawer,
		`<li class="s"><a href="/code/proj/target.txt">`,
		`<li class="s"><span><i class="icon i-symlink"></i>dangling</span>`,
		`<li class="s"><span><i class="icon i-symlink"></i>loop1</span>`)

	// The target appearing elsewhere links the symlink on the next push,
	// though nothing in its folder changed.
	e.write("new.txt", "n\n")
	e.commit("add target")
	e.push(false)
	e.render()
	mustContain(t, "docs listing", e.read(e.page("docs")), `<a href="/code/proj/new.txt">later</a>`)
	mustContain(t, "drawer", e.read(e.drawer()), `<a href="/code/proj/new.txt"><i class="icon i-symlink"></i>later</a>`)

	// Repointing a symlink updates the drawer too.
	e.rm("top")
	if err := os.Symlink("docs/a.md", filepath.Join(e.work, "top")); err != nil {
		t.Fatal(err)
	}
	e.commit("repoint")
	e.push(false)
	e.render()
	mustContain(t, "drawer", e.read(e.drawer()), `<a href="/code/proj/docs/a.md"><i class="icon i-symlink"></i>top</a>`)
}