package web
import (
"net/http"
"strings"
"testing"
"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)
// The grouping itself is tested in the beads package; what is left here is the
// pairing with the Beads tab and that milestones.html renders the rollup.
// milestoneFixture: six issues across two milestones. m1 carries the full
// hierarchy — a milestone-typed head, an epic with one (closed) subtask, and a
// loose bug; m2 has a single loose feature; one issue has no milestone label.
func milestoneFixture() *fakeSession {
issues := &browse.RowPage{
Columns: []string{"id", "title", "status", "priority", "issue_type", "assignee"},
Rows: [][]string{
{"i-m", "Mike", "open", "0", "milestone", ""},
{"i-e", "Echo", "open", "1", "epic", ""},
{"i-a", "Alpha", "closed", "1", "task", "alice"}, // subtask of i-e
{"i-b", "Bravo", "open", "0", "bug", "bob"},
{"i-c", "Charlie", "open", "2", "feature", ""},
{"i-d", "Delta", "open", "1", "task", ""}, // no milestone
},
Total: 6,
}
labels := &browse.RowPage{
Columns: []string{"issue_id", "label"},
Rows: [][]string{
{"i-m", "milestone:m1"},
{"i-e", "milestone:m1"},
{"i-a", "milestone:m1"},
{"i-b", "milestone:m1"},
{"i-c", "milestone:m2"},
{"i-b", "backend"}, // non-milestone label ignored by grouping
},
Total: 6,
}
deps := &browse.RowPage{
Columns: []string{"issue_id", "depends_on_issue_id", "type"},
Rows: [][]string{
{"i-a", "i-e", "parent-child"},
{"i-b", "i-a", "blocks"}, // non-hierarchy edge ignored by nesting
},
Total: 2,
}
statuses := &browse.RowPage{
Columns: []string{"name", "category"},
Rows: [][]string{{"open", "open"}, {"closed", "closed"}},
Total: 2,
}
return &fakeSession{
branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
tables: beadsTables(),
rowsByTable: map[string]*browse.RowPage{
"issues": issues,
"labels": labels,
"dependencies": deps,
"custom_statuses": statuses,
},
}
}
func TestMilestonesApplies(t *testing.T) {
// The milestone tab appears exactly where the beads tab does.
if (&milestonesView{}).Applies(beadsTables()) != (&beadsView{}).Applies(beadsTables()) {
t.Fatalf("milestones Applies should mirror beads Applies")
}
if (&milestonesView{}).Applies([]browse.TableInfo{{Name: "widgets"}}) {
t.Fatalf("milestones should not apply to an unrelated schema")
}
}
func TestMilestonesRender(t *testing.T) {
h := newHarness(t)
h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
h.browse.sess = milestoneFixture()
setViews(t, h, &beadsView{}, &milestonesView{})
rec := h.do("GET", "/~alice/db/view/milestones", nil, nil)
if rec.Code != http.StatusOK {
t.Fatalf("milestones view: got %d; body=%s", rec.Code, rec.Body.String())
}
body := rec.Body.String()
for _, want := range []string{
"· milestones", // page heading
"ms-title\">m1<", // milestone name
"1</b>/4 done", // rollup counts
"ms-issue head", // the milestone-typed issue leads the list
"ms-issue epic", // the epic row
"ms-issue child done", // its subtask, nested and closed
`class="r-sub">1/1<`, // the epic's subtask rollup
"Charlie", // an issue under m2
"carry no milestone", // unlabeled footnote
`view/milestones">Milestones`, // the tab link
"nav-link active", // the active tab marker
} {
if !strings.Contains(body, want) {
t.Errorf("milestones render missing %q", want)
}
}
}