Detail panes: path in the head, text flows around the poster

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 22:05:16 +02:00
co-authored by Claude Sonnet 5.5
parent b4fe931fa9
commit b1e368418b
6 changed files with 144 additions and 78 deletions
+56 -25
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func rowText(screen tcell.SimulationScreen, y, x0, x1 int) string {
@@ -16,7 +17,38 @@ func rowText(screen tcell.SimulationScreen, y, x0, x1 int) string {
return b.String()
}
func TestDetailPaneLayout(t *testing.T) {
func TestFlowWrap(t *testing.T) {
text := "one two three four five six seven eight nine ten"
// Narrow (10) for the first two lines, wide (25) afterwards.
got := flowWrap(text, 10, 25, 2)
want := []string{"one two", "three four", "five six seven eight nine", "ten"}
if strings.Join(got, "|") != strings.Join(want, "|") {
t.Errorf("got %q, want %q", got, want)
}
// Without a poster everything is wide.
if got := flowWrap(text, 10, 25, 0); got[0] != "one two three four five" {
t.Errorf("no poster: %q", got)
}
// Newlines are kept, blank lines too; trailing blank lines are dropped.
got = flowWrap("head\nfacts\n\nplot words\n", 20, 20, 0)
if strings.Join(got, "|") != "head|facts||plot words" {
t.Errorf("newlines: %q", got)
}
// Color tags have no width and escaped brackets count as one character.
got = flowWrap("[red]ab[-] cd ef", 5, 5, 0)
if strings.Join(got, "|") != "[red]ab[-] cd|ef" {
t.Errorf("tags: %q", got)
}
if got := flowWrap(tview.Escape("[1080p]")+" x", 9, 9, 0); len(got) != 1 {
t.Errorf("escaped brackets: %q", got)
}
// A word wider than the column is not split or lost.
if got := flowWrap("a verylongwordhere b", 5, 5, 0); strings.Join(got, "|") != "a|verylongwordhere|b" {
t.Errorf("long word: %q", got)
}
}
func TestDetailPaneFlowsAroundPoster(t *testing.T) {
a := newTestApp(t)
screen := tcell.NewSimulationScreen("")
if err := screen.Init(); err != nil {
@@ -25,52 +57,51 @@ func TestDetailPaneLayout(t *testing.T) {
defer screen.Fini()
screen.SetSize(60, 40)
plot := strings.Repeat("plot ", 220) // long enough to run past the poster
d := newDetailPane(a)
d.SetRect(0, 0, 60, 40)
d.Show(detailText{head: "[::b]The Matrix (1999)[::-]\nStatus: released", plot: "Plot text goes here"}, coverRef{})
d.Show(detailText{head: "[::b]The Matrix (1999)[::-]\nStatus: released\n", plot: plot}, coverRef{})
// Without a poster everything is one text.
// Without a poster the text uses the full width from the top.
d.Draw(screen)
if got := rowText(screen, 1, 1, 30); !strings.HasPrefix(got, "The Matrix (1999)") {
t.Errorf("first line = %q", got)
}
if got := rowText(screen, 3, 1, 30); !strings.HasPrefix(got, "Plot text goes here") {
t.Errorf("without a poster the plot follows the head: %q", got)
if got := strings.TrimSpace(rowText(screen, 5, 1, 59)); len(got) < 50 {
t.Errorf("without a poster the text should use the full width: %q", got)
}
// With a poster: head on the left, poster on the right, plot below both.
// With a poster (23×17 cells at the top right) the text runs beside it.
d.hasCover = true
d.cover.setImage(stripes(40, 60))
d.Draw(screen)
if d.coverW != 23 || d.coverH != 17 {
t.Fatalf("poster size %dx%d, want 23x17", d.coverW, d.coverH)
}
if got := rowText(screen, 1, 1, 30); !strings.HasPrefix(got, "The Matrix (1999)") {
t.Errorf("head moved: %q", got)
}
coverX := -1
for x := 59; x > 30; x-- {
if ch, _, st, _ := screen.GetContent(x, 1); ch != ' ' || st != tcell.StyleDefault {
coverX = x
break
if got := rowText(screen, 4, 1, 30); !strings.HasPrefix(got, "plot plot") {
t.Errorf("the plot starts beside the poster, right after the head: %q", got)
}
// Beside the poster the text stays left of it (34 columns), below it it uses the full width.
for y := 1; y <= d.coverH; y++ {
if got := strings.TrimRight(rowText(screen, y, 1, 35), " "); len([]rune(got)) > 34 {
t.Errorf("row %d runs into the poster: %q", y, got)
}
}
if coverX < 55 {
t.Errorf("no poster against the right edge (last drawn column %d)", coverX)
if got := strings.TrimSpace(rowText(screen, d.coverH+3, 1, 59)); len(got) < 50 {
t.Errorf("below the poster the text should use the full width: %q", got)
}
// The plot starts below the poster: 40*2/5 = 24 columns -> 24*3/2/2 = 18 rows.
plotRow := -1
for y := 1; y < 39; y++ {
if strings.HasPrefix(rowText(screen, y, 1, 30), "Plot text goes here") {
plotRow = y
break
}
}
if plotRow < 18 {
t.Errorf("plot at row %d, want below the poster", plotRow)
// The poster sits against the right edge of the pane.
if ch, _, st, _ := screen.GetContent(58, 1); ch == ' ' && st == tcell.StyleDefault {
t.Error("nothing drawn at the poster's right edge")
}
// A pane too short for the poster falls back to plain text.
d.SetRect(0, 0, 60, 14)
d.SetRect(0, 0, 60, 10)
d.Draw(screen)
if !d.combined {
if d.coverW != 0 {
t.Error("a short pane must drop the poster")
}
}