package beads import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sourcecraft.dev/bigbes/sr-ht-dolt/browse" ) // 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{ rowsByTable: map[string]*browse.RowPage{ "issues": issues, "labels": labels, "dependencies": deps, "custom_statuses": statuses, }, } } func TestMilestonesBuild(t *testing.T) { d, err := BuildMilestones(context.Background(), milestoneFixture(), "main") require.NoError(t, err) require.Len(t, d.Milestones, 2, "milestones = %+v", d.Milestones) assert.Equal(t, 1, d.Unlabeled) assert.Equal(t, 6, d.Total) m1 := d.Milestones[0] assert.Equal(t, "m1", m1.Name) assert.Equal(t, 4, m1.Total) assert.Equal(t, 1, m1.Done) assert.Equal(t, 3, m1.Open) assert.Equal(t, 25, m1.Pct()) // Hierarchy: the milestone-typed issue heads the list, the epic nests its // subtask, and the bug (whose only dep edge is "blocks") stays loose. require.Len(t, m1.Heads, 1, "m1 heads = %+v", m1.Heads) assert.Equal(t, "i-m", m1.Heads[0].ID) require.Len(t, m1.Epics, 1, "m1 epics = %+v", m1.Epics) epic := m1.Epics[0] assert.Equal(t, "i-e", epic.Card.ID) require.Len(t, epic.Children, 1, "epic = %+v", epic) assert.Equal(t, "i-a", epic.Children[0].ID) assert.Equal(t, 1, epic.Done) assert.Equal(t, 1, epic.Total) assert.Equal(t, "closed", epic.Children[0].Category, "i-a should be closed") require.Len(t, m1.Loose, 1, "m1 loose = %+v", m1.Loose) assert.Equal(t, "i-b", m1.Loose[0].ID) m2 := d.Milestones[1] assert.Equal(t, "m2", m2.Name) assert.Equal(t, 1, m2.Total) assert.Equal(t, 0, m2.Done) assert.Empty(t, m2.Heads) assert.Empty(t, m2.Epics) require.Len(t, m2.Loose, 1, "m2 hierarchy = %+v", m2) assert.Equal(t, "i-c", m2.Loose[0].ID) } // TestMilestonesEmpty: a beads DB with no milestone labels still produces a // (empty) rollup rather than erroring — the tab renders an empty state. func TestMilestonesEmpty(t *testing.T) { d, err := BuildMilestones(context.Background(), beadsFixture(), "main") require.NoError(t, err) assert.Empty(t, d.Milestones, "expected no milestones") assert.Equal(t, d.Total, d.Unlabeled, "with no milestone labels all issues are unlabeled") }