123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 | package render_test
import (
"fmt"
"strings"
"testing"
)
// TestStatsIncremental: an incremental push updates the root's line counts.
func TestStatsIncremental(t *testing.T) {
e := newEnv(t, "proj", "")
e.seed()
e.render()
e.write("src/extra.c", "int x;\n\n \nint y;\n")
e.commit("more C")
e.push(false)
e.render()
mustContain(t, "root stats", e.read(e.page("")), `>C <b>7</b></a>`)
mustContain(t, "C page", e.read("types/proj/code/c.html"), `href="/code/proj/src/extra.c">extra.c</a>`, "3 files")
mustNotContain(t, "file stats", e.read(e.page("src/extra.c")), `class="langs"`)
}
// TestSizeBars: each entry gets a Lines and a Size cell: its lines of
// text (as wc -l counts them) and its bytes, each with a ring filled as
// far as its share of the folder, lines split into code and docs.
func TestSizeBars(t *testing.T) {
e := newEnv(t, "proj", "")
// Root: 897 lines and 5129 bytes in all.
e.write("top.c", strings.Repeat("ab;\n", 512)) // 512 lines, 2 KiB
e.write("mixed/m.c", strings.Repeat("ab;\n", 256)) // 256 lines, 1 KiB
e.write("mixed/m.md", strings.Repeat("abcdefg\n", 128)) // 128 lines, 1 KiB
e.write("mixed/m.bin", "\x00"+strings.Repeat("z\n", 511)+"z") // no lines, 1 KiB
e.write("lib.min.js", "excluded\n") // counted all the same
e.commit("sizes")
e.push(false)
e.render()
cell := func(col, url, share, text, arcs string) string {
return fmt.Sprintf(`<td class="bar %s" title="%s">`+
`<a class="bar-link" href="%s" tabindex="-1" aria-hidden="true"><svg class="ring" viewBox="0 0 16 16" aria-hidden="true">`+
`<circle class="track" cx="8" cy="8" r="6"/>%s</svg></a>`+
`<span class="amount">%s</span><span class="vis">%s</span></td>`, col, share, url, arcs, text, share)
}
arc := func(class, length, off string) string {
if off != "" {
off = ` stroke-dashoffset="` + off + `"`
}
return fmt.Sprintf(`<circle class="%s" cx="8" cy="8" r="6" pathLength="100" stroke-dasharray="%s 100"%s/>`, class, length, off)
}
root := e.read(e.page(""))
mustContain(t, "root", root,
// Code then docs, each in its color, the docs arc starting where
// the code arc ends.
cell("lines", "/code/proj/mixed", "43% of this folder's lines: 256 lines of code, 128 lines of docs", "384 lines",
arc("code", "28.54", "")+arc("docs", "14.27", "-28.54"))+"\n"+
cell("size", "/code/proj/mixed", "60% of this folder's bytes", "3 KiB", arc("size", "59.89", "")),
cell("lines", "/code/proj/top.c", "57% of this folder's lines", "512 lines", arc("code", "57.08", ""))+"\n"+
cell("size", "/code/proj/top.c", "40% of this folder's bytes", "2 KiB", arc("size", "39.93", "")),
// A tiny share still gets a sliver.
cell("lines", "/code/proj/lib.min.js", "<1% of this folder's lines", "1 line", arc("code", "2.00", ""))+"\n"+
cell("size", "/code/proj/lib.min.js", "<1% of this folder's bytes", "9 B", arc("size", "2.00", "")))
// Binary data has a size but no lines: "0 lines" by an empty ring.
mustContain(t, "mixed", e.read(e.page("mixed")),
cell("lines", "/code/proj/mixed/m.bin", "0% of this folder's lines", "0 lines", "")+"\n"+cell("size", "/code/proj/mixed/m.bin", "33% of this folder's bytes", "1 KiB", arc("size", "33.33", "")))
}
// TestStatsEmptyRows: a row is left out when it has nothing to count, and a
// language whose files are all blank gets no pill.
func TestStatsEmptyRows(t *testing.T) {
for _, tc := range []struct {
name string
files map[string]string
code, docs bool
binary bool
notContains []string
}{
{"docs only", map[string]string{"README.md": "# Hi\n\nSome words.\n"}, false, true, false, nil},
{"code only", map[string]string{"main.c": "int x;\n"}, true, false, false, nil},
{"binary only", map[string]string{"bin.dat": "\x00\x01"}, false, false, true, nil},
{"none", map[string]string{"blank.c": "\n \n"}, false, false, false, nil},
{"blank language", map[string]string{"main.c": "int x;\n", "blank.lua": "\n\n", "x.md": "Hi\n"}, true, true, false, []string{"Lua"}},
} {
t.Run(tc.name, func(t *testing.T) {
e := newEnv(t, "proj", "")
for p, c := range tc.files {
e.write(p, c)
}
e.commit("files")
e.push(false)
e.render()
root := e.read(e.page(""))
check := func(has bool, s string) {
if has {
mustContain(t, tc.name, root, s)
} else {
mustNotContain(t, tc.name, root, s)
}
}
check(tc.code, `<div class="langs code"`)
check(tc.docs, `<div class="langs docs"`)
check(tc.binary, `<div class="langs binary"`)
if !tc.code && !tc.docs && !tc.binary {
mustNotContain(t, tc.name, root, `class="langs`)
}
for _, s := range tc.notContains {
mustNotContain(t, tc.name, root, `>`+s+` <b>`)
}
})
}
}
// TestTypePagesFollowTheTree: a type whose last file goes loses its page,
// and types folded past the first eight share an "Other" page that names
// each file's type.
func TestTypePagesFollowTheTree(t *testing.T) {
e := newEnv(t, "proj", "")
exts := []string{"png", "gif", "bmp", "ico", "webp", "tiff", "jpeg", "pdf", "zip", "bin"}
for i, ext := range exts {
e.write("f."+ext, strings.Repeat("\x00", 100-i))
}
e.write("main.c", "int x;\n")
e.commit("files")
e.push(false)
e.render()
other := e.read("types/proj/binary/other.html")
mustContain(t, "other page", other, `f.zip</a> <span class="target">ZIP</span>`, `f.bin</a> <span class="target">BIN</span>`)
mustNotContain(t, "other page", other, "f.png")
mustContain(t, "other pill", e.read(e.page("")), `title="183 B of other formats across 2 files">Other <b>183 B</b>`)
e.mustExist("types/proj/binary/png.html", "types/proj/code/c.html")
e.rm("main.c")
e.commit("no C")
e.push(false)
e.render()
e.mustNotExist("types/proj/code/c.html", "types/proj/code/c.html.gz", "types/proj/code")
e.mustExist("types/proj/binary/png.html")
}
// TestTypePageNestedPath: every folder on the way to a file is its own link.
func TestTypePageNestedPath(t *testing.T) {
e := newEnv(t, "proj", "")
e.write("a/b c/x.c", "int x;\n")
e.commit("nested")
e.push(false)
e.render()
mustContain(t, "C page", e.read("types/proj/code/c.html"),
`<a class="path-dir" href="/code/proj/a">a</a><span class="sep" aria-hidden="true">/</span>`+
`<a class="path-dir" href="/code/proj/a/b%20c">b c</a><span class="sep" aria-hidden="true">/</span>`+
`<a href="/code/proj/a/b%20c/x.c">x.c</a>`)
}
|