183 lines
5.7 KiB
Go
183 lines
5.7 KiB
Go
package tui
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
)
|
|
|
|
// TestNoHorizontalScroll verifies that rendered content never exceeds viewport width
|
|
func TestNoHorizontalScroll(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
screenWidth int
|
|
panelVisible bool
|
|
content string
|
|
}{
|
|
{
|
|
name: "long word with panel",
|
|
screenWidth: 120,
|
|
panelVisible: true,
|
|
content: strings.Repeat("x", 150),
|
|
},
|
|
{
|
|
name: "multiple long words without panel",
|
|
screenWidth: 100,
|
|
panelVisible: false,
|
|
content: strings.Repeat("superlongword ", 10),
|
|
},
|
|
{
|
|
name: "code block with panel",
|
|
screenWidth: 120,
|
|
panelVisible: true,
|
|
content: "```\n" + strings.Repeat("x", 100) + "\n```",
|
|
},
|
|
{
|
|
name: "URL without panel",
|
|
screenWidth: 80,
|
|
panelVisible: false,
|
|
content: "https://example.com/" + strings.Repeat("verylongpathsegment/", 5),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Calculate panel width
|
|
panelWidth := 0
|
|
if tt.panelVisible {
|
|
panelWidth = 30
|
|
if tt.screenWidth < 100 {
|
|
panelWidth = 25
|
|
} else if tt.screenWidth > 160 {
|
|
panelWidth = 40
|
|
}
|
|
}
|
|
|
|
// Calculate viewport width (from model.go)
|
|
viewportWidth := tt.screenWidth - 1
|
|
if tt.panelVisible {
|
|
viewportWidth = tt.screenWidth - panelWidth - 2
|
|
}
|
|
if viewportWidth < 20 {
|
|
viewportWidth = 20
|
|
}
|
|
|
|
// Calculate content width (from view.go)
|
|
contentW := tt.screenWidth - 4
|
|
if tt.panelVisible {
|
|
contentW = tt.screenWidth - panelWidth - 5
|
|
}
|
|
if contentW < 20 {
|
|
contentW = 20
|
|
}
|
|
|
|
// Wrap the content
|
|
wrapped := wrapText(tt.content, contentW)
|
|
|
|
// Check each line
|
|
lines := strings.Split(wrapped, "\n")
|
|
for i, line := range lines {
|
|
// Measure visible width (lipgloss.Width handles styling)
|
|
lineWidth := lipgloss.Width(line)
|
|
if lineWidth > viewportWidth {
|
|
t.Errorf("line %d width %d exceeds viewport width %d: %q",
|
|
i, lineWidth, viewportWidth, line[:min(50, len(line))])
|
|
}
|
|
if lineWidth > contentW {
|
|
t.Errorf("line %d width %d exceeds content width %d",
|
|
i, lineWidth, contentW)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestResponsivePanelToggle verifies no scroll when toggling panel
|
|
func TestResponsivePanelToggle(t *testing.T) {
|
|
screenWidth := 120
|
|
content := strings.Repeat("longword ", 20)
|
|
|
|
// Calculate widths with panel
|
|
panelWidth := 30
|
|
viewportWithPanel := screenWidth - panelWidth - 2
|
|
contentWithPanel := screenWidth - panelWidth - 5
|
|
|
|
// Calculate widths without panel
|
|
viewportWithoutPanel := screenWidth - 1
|
|
contentWithoutPanel := screenWidth - 4
|
|
|
|
// Wrap content for both scenarios
|
|
wrappedWithPanel := wrapText(content, contentWithPanel)
|
|
wrappedWithoutPanel := wrapText(content, contentWithoutPanel)
|
|
|
|
// Verify both fit within their respective viewports
|
|
for i, line := range strings.Split(wrappedWithPanel, "\n") {
|
|
if lipgloss.Width(line) > viewportWithPanel {
|
|
t.Errorf("with panel: line %d exceeds viewport", i)
|
|
}
|
|
}
|
|
|
|
for i, line := range strings.Split(wrappedWithoutPanel, "\n") {
|
|
if lipgloss.Width(line) > viewportWithoutPanel {
|
|
t.Errorf("without panel: line %d exceeds viewport", i)
|
|
}
|
|
}
|
|
|
|
// Verify that content without panel is wider (better use of space)
|
|
if contentWithoutPanel <= contentWithPanel {
|
|
t.Error("content width should increase when panel is hidden")
|
|
}
|
|
}
|
|
|
|
// TestEdgeCases verifies width handling at boundary conditions
|
|
func TestEdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
screenWidth int
|
|
expectMin bool // whether minimum width constraint should kick in
|
|
}{
|
|
{"minimum viable", 46, true}, // 25 (panel) + 1 + 20 (min viewport)
|
|
{"just above min", 50, false},
|
|
{"exactly 100", 100, false},
|
|
{"exactly 160", 160, false},
|
|
{"very large", 300, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
panelWidth := 30
|
|
if tt.screenWidth < 100 {
|
|
panelWidth = 25
|
|
} else if tt.screenWidth > 160 {
|
|
panelWidth = 40
|
|
}
|
|
|
|
viewportWidth := tt.screenWidth - panelWidth - 2
|
|
if viewportWidth < 20 {
|
|
viewportWidth = 20
|
|
}
|
|
|
|
if tt.expectMin && viewportWidth == 20 {
|
|
// Expected minimum enforcement
|
|
if tt.screenWidth-panelWidth-2 >= 20 {
|
|
t.Error("expected minimum width enforcement but calculation would allow larger")
|
|
}
|
|
}
|
|
|
|
// Verify viewport never exceeds available space (unless minimum enforced)
|
|
maxAllowed := tt.screenWidth - panelWidth - 1
|
|
if viewportWidth > maxAllowed && !tt.expectMin {
|
|
t.Errorf("viewport %d exceeds max allowed %d", viewportWidth, maxAllowed)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|