370 lines
13 KiB
Go
370 lines
13 KiB
Go
package tui
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestViewportWidthCalculation tests that viewport width calculations are consistent
|
|
// across all components to prevent horizontal scrolling.
|
|
func TestViewportWidthCalculation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
screenWidth int
|
|
panelVisible bool
|
|
panelWidth int
|
|
wantViewWidth int
|
|
wantContentW int
|
|
wantMarkdownW int
|
|
}{
|
|
{
|
|
name: "small screen with panel",
|
|
screenWidth: 80,
|
|
panelVisible: true,
|
|
panelWidth: 25,
|
|
wantViewWidth: 80 - 25 - 2, // screen - panel - separator
|
|
wantContentW: 80 - 25 - 5, // screen - panel - separator - padding
|
|
wantMarkdownW: 80 - 25 - 5,
|
|
},
|
|
{
|
|
name: "medium screen with panel",
|
|
screenWidth: 120,
|
|
panelVisible: true,
|
|
panelWidth: 30,
|
|
wantViewWidth: 120 - 30 - 2,
|
|
wantContentW: 120 - 30 - 5,
|
|
wantMarkdownW: 120 - 30 - 5,
|
|
},
|
|
{
|
|
name: "large screen with panel",
|
|
screenWidth: 160,
|
|
panelVisible: true,
|
|
panelWidth: 40,
|
|
wantViewWidth: 160 - 40 - 2,
|
|
wantContentW: 160 - 40 - 5,
|
|
wantMarkdownW: 160 - 40 - 5,
|
|
},
|
|
{
|
|
name: "small screen without panel",
|
|
screenWidth: 80,
|
|
panelVisible: false,
|
|
panelWidth: 0,
|
|
wantViewWidth: 80 - 1, // just separator
|
|
wantContentW: 80 - 1 - 3, // viewport - padding for markdown
|
|
wantMarkdownW: 80 - 1 - 3,
|
|
},
|
|
{
|
|
name: "large screen without panel",
|
|
screenWidth: 200,
|
|
panelVisible: false,
|
|
panelWidth: 0,
|
|
wantViewWidth: 200 - 1,
|
|
wantContentW: 200 - 1 - 3,
|
|
wantMarkdownW: 200 - 1 - 3,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Simulate the width calculation from model.go:WindowSizeMsg handler
|
|
panelWidth := tt.panelWidth
|
|
if panelWidth == 0 && tt.panelVisible {
|
|
// Calculate panel width based on screen width (from model.go:365-371)
|
|
panelWidth = 30
|
|
if tt.screenWidth < 100 {
|
|
panelWidth = 25
|
|
} else if tt.screenWidth > 160 {
|
|
panelWidth = 40
|
|
}
|
|
}
|
|
|
|
// Viewport width (from model.go:373-380)
|
|
viewportWidth := tt.screenWidth - 1
|
|
if tt.panelVisible {
|
|
viewportWidth = tt.screenWidth - panelWidth - 2
|
|
}
|
|
if viewportWidth < 20 {
|
|
viewportWidth = 20
|
|
}
|
|
|
|
// Content/markdown width (from model.go:382-386)
|
|
markdownWidth := viewportWidth - 3
|
|
if markdownWidth < 20 {
|
|
markdownWidth = 20
|
|
}
|
|
|
|
// Content width for rendering (from view.go:422-429)
|
|
contentW := tt.screenWidth - 4
|
|
if tt.panelVisible {
|
|
contentW = tt.screenWidth - panelWidth - 5
|
|
}
|
|
if contentW < 20 {
|
|
contentW = 20
|
|
}
|
|
|
|
// Verify consistency
|
|
if markdownWidth != tt.wantMarkdownW {
|
|
t.Errorf("markdown width = %d, want %d", markdownWidth, tt.wantMarkdownW)
|
|
}
|
|
|
|
if viewportWidth != tt.wantViewWidth {
|
|
t.Errorf("viewport width = %d, want %d", viewportWidth, tt.wantViewWidth)
|
|
}
|
|
|
|
if contentW != tt.wantContentW {
|
|
t.Errorf("content width = %d, want %d", contentW, tt.wantContentW)
|
|
}
|
|
|
|
// CRITICAL: viewport width should never exceed screen width minus panel
|
|
maxAllowedWidth := tt.screenWidth - 1
|
|
if tt.panelVisible {
|
|
maxAllowedWidth = tt.screenWidth - panelWidth - 1
|
|
}
|
|
if viewportWidth > maxAllowedWidth {
|
|
t.Errorf("viewport width %d exceeds max allowed %d - will cause horizontal scroll",
|
|
viewportWidth, maxAllowedWidth)
|
|
}
|
|
|
|
// Content width should be <= viewport width
|
|
if contentW > viewportWidth {
|
|
t.Errorf("content width %d > viewport width %d - will cause horizontal scroll",
|
|
contentW, viewportWidth)
|
|
}
|
|
|
|
// Markdown width should be <= viewport width
|
|
if markdownWidth > viewportWidth {
|
|
t.Errorf("markdown width %d > viewport width %d - will cause horizontal scroll",
|
|
markdownWidth, viewportWidth)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestResponsiveWidthToggle tests that toggling the side panel maintains proper widths
|
|
func TestResponsiveWidthToggle(t *testing.T) {
|
|
screenWidth := 120
|
|
panelWidth := 30
|
|
|
|
// Panel visible
|
|
viewportWithPanel := screenWidth - panelWidth - 2
|
|
contentWithPanel := screenWidth - panelWidth - 5
|
|
|
|
// Panel hidden
|
|
viewportWithoutPanel := screenWidth - 1
|
|
contentWithoutPanel := screenWidth - 4
|
|
|
|
// Widths should increase when panel is hidden
|
|
if viewportWithoutPanel <= viewportWithPanel {
|
|
t.Errorf("viewport should be wider when panel is hidden: %d <= %d",
|
|
viewportWithoutPanel, viewportWithPanel)
|
|
}
|
|
|
|
if contentWithoutPanel <= contentWithPanel {
|
|
t.Errorf("content should be wider when panel is hidden: %d <= %d",
|
|
contentWithoutPanel, contentWithPanel)
|
|
}
|
|
|
|
// Neither should exceed screen width
|
|
if viewportWithoutPanel > screenWidth {
|
|
t.Errorf("viewport without panel %d exceeds screen width %d",
|
|
viewportWithoutPanel, screenWidth)
|
|
}
|
|
|
|
if viewportWithPanel > screenWidth-panelWidth {
|
|
t.Errorf("viewport with panel %d exceeds available space %d",
|
|
viewportWithPanel, screenWidth-panelWidth)
|
|
}
|
|
}
|
|
|
|
// TestMinimumWidthConstraints tests that minimum width constraints prevent negative layouts
|
|
func TestMinimumWidthConstraints(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
screenWidth int
|
|
}{
|
|
{"tiny screen", 40},
|
|
{"very small screen", 60},
|
|
{"small screen", 80},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
panelWidth := 25 // minimum panel width
|
|
minWidth := 20 // minimum content width
|
|
|
|
// Calculate viewport width with panel
|
|
viewportWidth := tt.screenWidth - panelWidth - 2
|
|
if viewportWidth < minWidth {
|
|
viewportWidth = minWidth
|
|
}
|
|
|
|
// Calculate content width with panel
|
|
contentWidth := tt.screenWidth - panelWidth - 5
|
|
if contentWidth < minWidth {
|
|
contentWidth = minWidth
|
|
}
|
|
|
|
// Verify minimums are respected
|
|
if viewportWidth < minWidth {
|
|
t.Errorf("viewport width %d below minimum %d", viewportWidth, minWidth)
|
|
}
|
|
|
|
if contentWidth < minWidth {
|
|
t.Errorf("content width %d below minimum %d", contentWidth, minWidth)
|
|
}
|
|
|
|
// Even with minimum constraints, total shouldn't exceed screen
|
|
totalWidth := panelWidth + 1 + viewportWidth
|
|
if totalWidth > tt.screenWidth && tt.screenWidth >= minWidth+panelWidth+1 {
|
|
t.Errorf("total width %d exceeds screen width %d", totalWidth, tt.screenWidth)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRenderedTextWidth simulates actual rendered text to ensure it fits within viewport
|
|
func TestRenderedTextWidth(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
screenWidth int
|
|
panelWidth int
|
|
text string
|
|
}{
|
|
{
|
|
name: "long line with panel",
|
|
screenWidth: 120,
|
|
panelWidth: 30,
|
|
text: strings.Repeat("x", 100),
|
|
},
|
|
{
|
|
name: "long line without panel",
|
|
screenWidth: 120,
|
|
panelWidth: 0,
|
|
text: strings.Repeat("x", 120),
|
|
},
|
|
{
|
|
name: "short line",
|
|
screenWidth: 80,
|
|
panelWidth: 25,
|
|
text: "short text",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Calculate available content width
|
|
availableWidth := tt.screenWidth - 4
|
|
if tt.panelWidth > 0 {
|
|
availableWidth = tt.screenWidth - tt.panelWidth - 5
|
|
}
|
|
if availableWidth < 20 {
|
|
availableWidth = 20
|
|
}
|
|
|
|
// Simulate wrapText behavior
|
|
wrapped := wrapText(tt.text, availableWidth)
|
|
|
|
// Check each line fits
|
|
lines := strings.Split(wrapped, "\n")
|
|
for i, line := range lines {
|
|
if len(line) > availableWidth {
|
|
t.Errorf("line %d length %d exceeds available width %d",
|
|
i, len(line), availableWidth)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLayoutConsistency verifies that all width calculations are consistent
|
|
func TestLayoutConsistency(t *testing.T) {
|
|
// Test various screen sizes
|
|
for screenWidth := 40; screenWidth <= 200; screenWidth += 10 {
|
|
t.Run("screen_width", func(t *testing.T) {
|
|
// Determine panel width (from model.go logic)
|
|
panelWidth := 30
|
|
if screenWidth < 100 {
|
|
panelWidth = 25
|
|
} else if screenWidth > 160 {
|
|
panelWidth = 40
|
|
}
|
|
|
|
// Test with panel visible
|
|
t.Run("with_panel", func(t *testing.T) {
|
|
// Viewport width calculation (from model.go:373-380)
|
|
viewportWidth := screenWidth - panelWidth - 2
|
|
if viewportWidth < 20 {
|
|
viewportWidth = 20
|
|
}
|
|
|
|
// Content width calculation (from model.go:382-386)
|
|
markdownWidth := viewportWidth - 3
|
|
if markdownWidth < 20 {
|
|
markdownWidth = 20
|
|
}
|
|
|
|
contentWidth := screenWidth - panelWidth - 5
|
|
if contentWidth < 20 {
|
|
contentWidth = 20
|
|
}
|
|
|
|
// All widths should be consistent
|
|
if contentWidth > viewportWidth {
|
|
t.Errorf("content %d > viewport %d", contentWidth, viewportWidth)
|
|
}
|
|
|
|
if markdownWidth > viewportWidth {
|
|
t.Errorf("markdown %d > viewport %d", markdownWidth, viewportWidth)
|
|
}
|
|
|
|
// For very small screens, the layout might exceed screen width
|
|
// This is expected - the minimum viewport width takes precedence
|
|
minRequiredWidth := panelWidth + 1 + 20 // panel + separator + min viewport
|
|
if screenWidth >= minRequiredWidth {
|
|
// Only check total width if screen is large enough
|
|
totalWidth := panelWidth + 1 + viewportWidth
|
|
if totalWidth > screenWidth {
|
|
t.Errorf("total layout %d exceeds screen %d", totalWidth, screenWidth)
|
|
}
|
|
}
|
|
})
|
|
|
|
// Test without panel
|
|
t.Run("without_panel", func(t *testing.T) {
|
|
// Viewport width calculation
|
|
viewportWidth := screenWidth - 1
|
|
if viewportWidth < 20 {
|
|
viewportWidth = 20
|
|
}
|
|
|
|
// Content width calculation
|
|
markdownWidth := viewportWidth - 3
|
|
if markdownWidth < 20 {
|
|
markdownWidth = 20
|
|
}
|
|
|
|
contentWidth := screenWidth - 4
|
|
if contentWidth < 20 {
|
|
contentWidth = 20
|
|
}
|
|
|
|
// All widths should be consistent
|
|
if contentWidth > viewportWidth {
|
|
t.Errorf("content %d > viewport %d", contentWidth, viewportWidth)
|
|
}
|
|
|
|
if markdownWidth > viewportWidth {
|
|
t.Errorf("markdown %d > viewport %d", markdownWidth, viewportWidth)
|
|
}
|
|
|
|
// For very small screens, minimum width takes precedence
|
|
if screenWidth >= 21 { // 1 + min viewport (20)
|
|
if viewportWidth > screenWidth {
|
|
t.Errorf("viewport %d exceeds screen %d", viewportWidth, screenWidth)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|