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/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) } } }