115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
package main
|
|
|
|
// The frame is the one place where an off-by-one becomes a broken screen, so it
|
|
// is checked rather than eyeballed: every rendered line must be exactly the
|
|
// width asked for, at every size, with and without tabs.
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
func lines(s string) []string { return strings.Split(s, "\n") }
|
|
|
|
func TestFrameEveryLineIsExactWidth(t *testing.T) {
|
|
for w := 10; w <= 90; w++ {
|
|
for h := 3; h <= 20; h++ {
|
|
for _, tabs := range [][]string{nil, {"one", "two"}, {"a"}} {
|
|
for _, focused := range []bool{false, true} {
|
|
p := panel{title: "tasks", tabs: tabs, width: w, height: h, focused: focused}
|
|
out := p.render([]string{"first", "second"})
|
|
got := lines(out)
|
|
if len(got) != h {
|
|
t.Fatalf("width %d height %d: got %d lines, want %d", w, h, len(got), h)
|
|
}
|
|
for i, ln := range got {
|
|
if lipgloss.Width(ln) != w {
|
|
t.Fatalf("width %d height %d tab=%v focus=%v line %d: width %d (%q)",
|
|
w, h, tabs, focused, i, lipgloss.Width(ln), ln)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFrameBodyIsClippedNotWrapped(t *testing.T) {
|
|
p := panel{title: "t", width: 20, height: 4}
|
|
out := p.render([]string{"a line that is much too long for the frame"})
|
|
for i, ln := range lines(out) {
|
|
if lipgloss.Width(ln) != 20 {
|
|
t.Fatalf("line %d is %d wide, want 20: %q", i, lipgloss.Width(ln), ln)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTruncateVisibleKeepsStylingAndWidth(t *testing.T) {
|
|
styled := lipgloss.NewStyle().Bold(true).Render("abcdefghij")
|
|
got := truncateVisible(styled, 5)
|
|
if lipgloss.Width(got) > 5 {
|
|
t.Fatalf("truncated width %d, want at most 5: %q", lipgloss.Width(got), got)
|
|
}
|
|
if !strings.Contains(got, "…") {
|
|
t.Fatalf("truncation should say it truncated: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestBannerIsDrawnOnceAndKeepsTheHeight(t *testing.T) {
|
|
defer func(prev string) { bannerText = prev }(bannerText)
|
|
bannerText = "no worktrees — this folder is not a git repository"
|
|
const w, h = 80, 24
|
|
|
|
// A screen with three panels side by side, the way the board is drawn.
|
|
one := panel{title: "a", width: 26, height: h - 2}.render([]string{"x"})
|
|
two := panel{title: "b", width: 26, height: h - 2}.render([]string{"y"})
|
|
three := panel{title: "c", width: 26, height: h - 2}.render([]string{"z"})
|
|
inner := strings.Join([]string{
|
|
strings.Repeat(" ", w),
|
|
sideBySide(one, two, three),
|
|
"footer",
|
|
}, "\n")
|
|
|
|
b := bannerModel{inner: stubModel{view: inner}, width: w, height: h}
|
|
got := lines(b.View())
|
|
if len(got) != h {
|
|
t.Fatalf("got %d lines, want %d", len(got), h)
|
|
}
|
|
if n := strings.Count(strings.Join(got, "\n"), "no worktrees"); n != 1 {
|
|
t.Fatalf("the warning appears %d times, want once", n)
|
|
}
|
|
if !strings.Contains(strings.Join(got, "\n"), "footer") {
|
|
t.Fatal("the key hints were dropped; only the title line should give way")
|
|
}
|
|
if lipgloss.Width(got[0]) != w {
|
|
t.Fatalf("the warning line is %d wide, want %d", lipgloss.Width(got[0]), w)
|
|
}
|
|
}
|
|
|
|
type stubModel struct{ view string }
|
|
|
|
func (s stubModel) Init() tea.Cmd { return nil }
|
|
func (s stubModel) Update(tea.Msg) (tea.Model, tea.Cmd) { return s, nil }
|
|
func (s stubModel) View() string { return s.view }
|
|
|
|
func sideBySide(parts ...string) string {
|
|
rows := make([][]string, len(parts))
|
|
for i, p := range parts {
|
|
rows[i] = lines(p)
|
|
}
|
|
var out []string
|
|
for r := range rows[0] {
|
|
var b strings.Builder
|
|
for _, cols := range rows {
|
|
if r < len(cols) {
|
|
b.WriteString(cols[r])
|
|
}
|
|
}
|
|
out = append(out, b.String())
|
|
}
|
|
return strings.Join(out, "\n")
|
|
}
|