Files
superwork/tui/internal/tui/render_repro_test.go
T
2026-06-23 05:02:15 +08:00

120 lines
4.2 KiB
Go

package tui
import (
"strings"
"testing"
"charm.land/lipgloss/v2"
"superwork-tui/internal/issue"
)
// TestBoardView_NeverExceedsTerminalHeight reproduces the interactive overflow
// bug: with double-width CJK titles, cards wrap inside the fixed-width column box
// to more rows than windowCards budgeted (it measured pre-wrap heights), so the
// board grew taller than the terminal and the altscreen renderer pushed headers
// and short columns off the top. The board must always fit m.height.
func TestBoardView_NeverExceedsTerminalHeight(t *testing.T) {
mk := func(n int, col issue.Column, title string) issue.Issue {
return issue.Issue{Number: n, Title: title, Column: col, PR: "240", Branch: "feature/abc"}
}
var issues []issue.Issue
for i := 0; i < 11; i++ {
issues = append(issues, mk(238-i, issue.ColumnTodo, "支持部署时使用客户自定义域名并自动签发证书与续期"))
}
for i := 0; i < 4; i++ {
issues = append(issues, mk(239-i, issue.ColumnReview, "分销裂变系统V1:一级分销 + 邀请注册归因 + 订单返佣 + T+30 结算"))
}
for i := 0; i < 101; i++ {
issues = append(issues, mk(226-i, issue.ColumnDone, "充值页面前端完全重构为电商风格(含 PC/移动端专用 UI)"))
}
for _, dims := range [][2]int{{250, 55}, {200, 50}, {180, 40}, {160, 30}} {
for _, colIdx := range []int{0, 3} {
m := Model{
state: stateLoaded,
issues: issues,
buckets: bucketize(issues),
width: dims[0],
height: dims[1],
colIdx: colIdx,
statusMsg: "webhook:端口 17421 已被占用",
hasChanges: true,
}
out := m.boardView()
if h := lipgloss.Height(out); h > m.height {
t.Errorf("dims=%v colIdx=%d: boardView height %d exceeds terminal height %d", dims, colIdx, h, m.height)
}
}
}
}
func TestBoardView_ShowsAllNonEmptyColumns(t *testing.T) {
var issues []issue.Issue
for i := 0; i < 11; i++ {
issues = append(issues, issue.Issue{Number: 1000 + i, Title: "TODOCARD", Column: issue.ColumnTodo})
}
for i := 0; i < 4; i++ {
issues = append(issues, issue.Issue{Number: 2000 + i, Title: "REVIEWCARD", Column: issue.ColumnReview})
}
for i := 0; i < 101; i++ {
issues = append(issues, issue.Issue{Number: 3000 + i, Title: "DONECARD", Column: issue.ColumnDone})
}
m := Model{
state: stateLoaded,
issues: issues,
buckets: bucketize(issues),
width: 200,
height: 50,
}
out := m.boardView()
for _, want := range []string{"TODOCARD", "REVIEWCARD", "DONECARD"} {
if !strings.Contains(out, want) {
t.Errorf("%s missing from boardView", want)
}
}
}
// Realistic: long wrapping Chinese titles, badges, screenshot-like dimensions,
// and selection on the done column (rightmost). Reproduces the real board shape.
func TestBoardView_RealisticDistribution(t *testing.T) {
mk := func(n int, col issue.Column, title string) issue.Issue {
return issue.Issue{
Number: n, Title: title, Column: col,
SpecFile: "docs/superpowers/specs/x/spec.md",
PlanFile: "docs/superpowers/plans/x/plan.md",
PR: "240", Branch: "feature/abc123",
}
}
var issues []issue.Issue
issues = append(issues, mk(238, issue.ColumnTodo, "TODOMARK 支持部署时使用客户自定义域名"))
for i := 0; i < 10; i++ {
issues = append(issues, mk(220-i, issue.ColumnTodo, "✨ 裂变机制:邀请拉新双向激励(邀请码 + token credit 返利)"))
}
issues = append(issues, mk(239, issue.ColumnReview, "REVIEWMARK 分销裂变系统V1"))
for i := 0; i < 3; i++ {
issues = append(issues, mk(232-i, issue.ColumnReview, "✨ 实现 Claude Code /rewind 效果:会话与代码/沙箱状态回退"))
}
for i := 0; i < 101; i++ {
issues = append(issues, mk(222-i, issue.ColumnDone, "DONEMARK 用户内容免责协议"))
}
for _, dims := range [][2]int{{250, 55}, {200, 50}, {180, 40}} {
for _, colIdx := range []int{0, 3} {
m := Model{
state: stateLoaded,
issues: issues,
buckets: bucketize(issues),
width: dims[0],
height: dims[1],
colIdx: colIdx,
}
out := m.boardView()
for _, want := range []string{"TODOMARK", "REVIEWMARK", "DONEMARK"} {
if !strings.Contains(out, want) {
t.Errorf("dims=%v colIdx=%d: %q missing from boardView", dims, colIdx, want)
}
}
}
}
}