package web
import (
"fmt"
"testing"
)
// The strips never draw more lanes than there is room for: files fold
// into their directories, the busy directories open toward their files,
// and the touches follow them.
func TestTreeColumnsFoldIntoDirectoriesToFit(t *testing.T) {
act := activityOut{Since: "2026-01-01", Until: "2026-01-10"}
for d := range 10 {
for f := range 100 {
act.Paths = append(act.Paths, fmt.Sprintf("src/d%d/f%03d.c", d, f))
}
}
act.Paths = append(act.Paths, "README")
// d3's files 50 and 51 are touched, and so is README.
act.Touches = []activityTouch{
{Day: 2, Path: 350, Author: 0},
{Day: 2, Path: 351, Author: 0},
{Day: 4, Path: 1000, Author: 1},
}
got, folded := foldColumns(act, 50)
if !folded {
t.Fatal("FAIL: 1001 paths against a limit of 50 did not fold")
}
if len(got.Paths) > 50 {
t.Fatalf("FAIL: %d columns after the fold, want at most 50", len(got.Paths))
}
// The directories nobody touched stay one column each; the touched
// one opens down to the two files and gathers the rest.
has := map[string]bool{}
for _, n := range got.Paths {
has[n] = true
}
for _, want := range []string{"src/d0/", "src/d9/", "src/d3/f050.c", "src/d3/f051.c", "src/d3/(other)", "README"} {
if !has[want] {
t.Fatalf("FAIL: no column %q in %v", want, got.Paths)
}
}
if len(got.Touches) != 3 {
t.Fatalf("FAIL: %d touches, want 3: %+v", len(got.Touches), got.Touches)
}
if p := got.Paths[got.Touches[0].Path]; p != "src/d3/f050.c" {
t.Fatalf("FAIL: the first touch lands on %q", p)
}
if p := got.Paths[got.Touches[2].Path]; p != "README" {
t.Fatalf("FAIL: the README touch lands on %q", p)
}
// Under the limit nothing moves.
if same, folded := foldColumns(act, 2000); folded || len(same.Paths) != len(act.Paths) {
t.Fatal("FAIL: a tree under the limit was folded")
}
// Touches that land on the same (day, column, person) become one.
two := activityOut{Paths: []string{"a/x", "a/y", "b/z"}, Touches: []activityTouch{
{Day: 1, Path: 0, Author: 0}, {Day: 1, Path: 1, Author: 0}, {Day: 1, Path: 2, Author: 0},
}}
got, _ = foldColumns(two, 2)
if len(got.Paths) != 2 || len(got.Touches) != 2 {
t.Fatalf("FAIL: %v %+v", got.Paths, got.Touches)
}
// A flat tree is one column of root files.
flat := activityOut{}
for f := range 500 {
flat.Paths = append(flat.Paths, fmt.Sprintf("f%03d", f))
}
flat.Touches = []activityTouch{{Day: 0, Path: 499, Author: 0}}
got, _ = foldColumns(flat, 50)
if len(got.Paths) != 1 || got.Touches[0].Path != 0 {
t.Fatalf("FAIL: flat tree folded to %d columns, touch on %d", len(got.Paths), got.Touches[0].Path)
}
// A tree too wide even at its top level keeps its heaviest
// directories and gathers the rest, the files at the root among them.
wide := activityOut{}
for d := range 40 {
wide.Paths = append(wide.Paths, fmt.Sprintf("d%02d/a.c", d), fmt.Sprintf("d%02d/b.c", d))
}
wide.Paths = append(wide.Paths, "zz", "zzz")
for d := range 40 {
for k := 0; k < 1+d; k++ {
wide.Touches = append(wide.Touches, activityTouch{Day: k, Path: d * 2, Author: 0})
}
}
got, folded = foldColumns(wide, 16)
kept := map[string]bool{}
for _, n := range got.Paths {
kept[n] = true
}
if !folded || len(got.Paths) > 16 || !kept["(other)"] {
t.Fatalf("FAIL: %d columns %v", len(got.Paths), got.Paths)
}
for _, d := range []string{"d39/", "d38/", "d37/"} {
if !kept[d] {
t.Fatalf("FAIL: the busy directory %s was gathered into other: %v", d, got.Paths)
}
}
if kept["d00/"] {
t.Fatalf("FAIL: a directory with almost no work has a column of its own: %v", got.Paths)
}
t.Log("ok")
}