Gim/internal/editor/integration_scroll_test.go
Hayden Hargreaves db70ca39f1 feat: implementing vertical scrolling, tested
Heavy on the AI...
2026-02-12 22:40:17 -07:00

188 lines
5.3 KiB
Go

package editor
import (
"fmt"
"testing"
"git.gophernest.net/azpect/TextEditor/internal/action"
)
// NOTE: AI Generated tests
// generateLines creates n lines of content
func generateLines(n int) []string {
lines := make([]string, n)
for i := range n {
lines[i] = fmt.Sprintf("line %d", i+1)
}
return lines
}
func TestScrollBasic(t *testing.T) {
t.Run("small file does not scroll", func(t *testing.T) {
// 10 lines, viewport 24 -> no scrolling needed
lines := generateLines(10)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 24)
sendKeys(tm, "G") // go to bottom
m := getFinalModel(t, tm)
if m.ScrollY() != 0 {
t.Errorf("ScrollY() = %d, want 0", m.ScrollY())
}
})
t.Run("scrolls down when cursor moves past bottom margin", func(t *testing.T) {
// 50 lines, viewport 20, scrollOff 8
// Viewport shows lines 0-18 (19 lines, -1 for status bar)
// Safe zone: lines 8 to 10 (19-1-8=10)
// Moving to line 11+ should trigger scroll
lines := generateLines(50)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
// Move down 15 times to get to line 15
for range 15 {
sendKeys(tm, "j")
}
m := getFinalModel(t, tm)
if m.CursorY() != 15 {
t.Errorf("CursorY() = %d, want 15", m.CursorY())
}
// With scrollOff=8, viewport=19, cursor at 15 means:
// cursor should be at position 10 from top (19-1-8=10)
// so scrollY = 15 - 10 = 5
if m.ScrollY() < 1 {
t.Errorf("ScrollY() = %d, want > 0 (should have scrolled)", m.ScrollY())
}
})
t.Run("scrolls up when cursor moves past top margin", func(t *testing.T) {
// Start at line 20, move up
lines := generateLines(50)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 20}, 80, 20)
// First, let the model adjust (it will scroll to show cursor)
// Then move up 15 times
for range 15 {
sendKeys(tm, "k")
}
m := getFinalModel(t, tm)
if m.CursorY() != 5 {
t.Errorf("CursorY() = %d, want 5", m.CursorY())
}
// Cursor at line 5 with scrollOff=8 means scrollY should be 0
// (can't scroll negative, and cursor is within safe zone from top)
if m.ScrollY() != 0 {
t.Errorf("ScrollY() = %d, want 0", m.ScrollY())
}
})
t.Run("G jumps to bottom and scrolls", func(t *testing.T) {
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
sendKeys(tm, "G")
m := getFinalModel(t, tm)
if m.CursorY() != 99 {
t.Errorf("CursorY() = %d, want 99", m.CursorY())
}
// With 100 lines and viewport 19, max scrollY = 100 - 19 = 81
// Cursor at 99 with scrollOff=8 means cursor at position 10 from top
// scrollY = 99 - 10 = 89, but clamped to maxScroll = 81
if m.ScrollY() != 81 {
t.Errorf("ScrollY() = %d, want 81", m.ScrollY())
}
})
t.Run("gg jumps to top and scrolls", func(t *testing.T) {
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 50}, 80, 20)
sendKeys(tm, "g", "g")
m := getFinalModel(t, tm)
if m.CursorY() != 0 {
t.Errorf("CursorY() = %d, want 0", m.CursorY())
}
if m.ScrollY() != 0 {
t.Errorf("ScrollY() = %d, want 0", m.ScrollY())
}
})
}
func TestScrollEdgeCases(t *testing.T) {
t.Run("scrollY never goes negative", func(t *testing.T) {
lines := generateLines(50)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
// Try to move up from top
for range 5 {
sendKeys(tm, "k")
}
m := getFinalModel(t, tm)
if m.ScrollY() < 0 {
t.Errorf("ScrollY() = %d, want >= 0", m.ScrollY())
}
})
t.Run("scrollY clamped to max scroll", func(t *testing.T) {
lines := generateLines(30)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
sendKeys(tm, "G")
m := getFinalModel(t, tm)
// 30 lines, viewport 19 -> maxScroll = 30 - 19 = 11
maxScroll := 30 - 19
if m.ScrollY() > maxScroll {
t.Errorf("ScrollY() = %d, want <= %d", m.ScrollY(), maxScroll)
}
})
t.Run("cursor stays visible after delete at bottom", func(t *testing.T) {
lines := generateLines(30)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 29}, 80, 20)
// Delete some lines at bottom
sendKeys(tm, "d", "d", "d", "d")
m := getFinalModel(t, tm)
// Cursor should still be visible
viewportHeight := 19
if m.CursorY() < m.ScrollY() || m.CursorY() >= m.ScrollY()+viewportHeight {
t.Errorf("Cursor at %d not visible in viewport [%d, %d)",
m.CursorY(), m.ScrollY(), m.ScrollY()+viewportHeight)
}
})
}
func TestScrollWithCount(t *testing.T) {
t.Run("5j scrolls appropriately", func(t *testing.T) {
lines := generateLines(50)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 5}, 80, 20)
sendKeys(tm, "1", "0", "j") // move down 10 lines
m := getFinalModel(t, tm)
if m.CursorY() != 15 {
t.Errorf("CursorY() = %d, want 15", m.CursorY())
}
// Should have scrolled since we moved past the safe zone
if m.ScrollY() == 0 {
t.Errorf("ScrollY() = %d, want > 0", m.ScrollY())
}
})
t.Run("5k scrolls appropriately", func(t *testing.T) {
lines := generateLines(50)
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 25}, 80, 20)
sendKeys(tm, "1", "5", "k") // move up 15 lines
m := getFinalModel(t, tm)
if m.CursorY() != 10 {
t.Errorf("CursorY() = %d, want 10", m.CursorY())
}
})
}