Pad/internal/editor/wrap_apply_test.go
Greg Pomerantz 06b1444207 gofmt: format all remaining files with the go1.27 toolchain
The tree was formatted with an older gofmt; go1.27's gofmt additionally
wants: EOF exactly one newline (no trailing blank lines), imports sorted
alphabetically within a block, mixed-precedence binary expressions
re-spaced for grouping ((a+b)/c), single-field composite literals
un-aligned, adjacent one-line method signatures aligned, and one-line
bodies containing a compound statement expanded. Applied repo-wide
(31 files under internal/); pure formatting, no semantic changes —
build and the full test suite pass.
2026-08-23 10:03:27 -04:00

205 lines
6.8 KiB
Go

package editor
import (
"strings"
"testing"
"pad/internal/io/pool/types"
"pad/internal/ui"
)
// TestApplyWrapCounts_Grouping verifies that a shaped window's
// VisualLineStarts (window-relative byte offsets, one per visual line,
// including the initial 0) are attributed to the correct logical lines:
// a wrapped logical line gets its true visual-line count, and lines the
// layout does not cover keep their previous counts (over-stale is safe,
// under-attributing is not).
func TestApplyWrapCounts_Grouping(t *testing.T) {
// Whole-file buffer; the shaped window covers lines 5..8.
var lines []string
for i := 0; i < 12; i++ {
lines = append(lines, "line-content-"+strings.Repeat("x", i%7))
}
// The windowed lines get distinctive shapes.
lines[5] = "A very long line that wraps three times"
lines[6] = "short"
lines[7] = "" // empty line
lines[8] = "another line that wraps twice"
content := strings.Join(lines, "\n") + "\n"
cb := newTestBufferForWrapWholeFile(content)
// Distinct seed counts so any (un)set line is distinguishable.
for i := 0; i < cb.WrapIndex.Len(); i++ {
cb.WrapIndex.Set(i, int32(9))
}
// The shaped window covers lines 5..8 (their content plus terminators).
winStart := byteOffsetOfLine(content, 5)
windowText := strings.Join(lines[5:9], "\n") + "\n"
off := map[string]int{}
crs := 0
for _, l := range lines[5:9] {
off[l] = crs
crs += len(l) + 1
}
// Fabricated visual line starts (window-relative), as the shaper would
// emit them: 0 first, then one per wrapped continuation, then one per
// following logical line start.
starts := []int{
0, // line 5 visual 1
20, // line 5 wraps
35, // line 5 wraps again
off["short"], // line 6
off[""], // line 7 (empty)
off["another line that wraps twice"], // line 8
off["another line that wraps twice"] + 19, // line 8 wraps
}
TheState = NewState()
TheState.Editor.ChunkedBuffer = cb
TheState.Editor.IMEWindowText = windowText
TheState.Editor.IMEWindowStartByte = winStart
TheState.Editor.EditSeq = 42
fb := ui.LayoutFeedback{
GlyphLayout: ui.GlyphLayout{VisualLineStarts: starts, LineHeight: 20},
WindowText: windowText,
WindowStartByte: winStart,
WindowStartLine: 5,
EditSeq: 42,
}
TheState.applyWrapCounts(fb)
want := map[int]int32{5: 3, 6: 1, 7: 1, 8: 2}
for line, wc := range want {
if got := cb.WrapIndex.Get(line); got != wc {
t.Errorf("line %d count = %d, want %d", line, got, wc)
}
}
// Untouched lines keep their seed count.
for _, line := range []int{0, 1, 2, 3, 4, 9, 10, 11} {
if got := cb.WrapIndex.Get(line); got != 9 {
t.Errorf("line %d count = %d, want untouched 9", line, got)
}
}
}
// TestApplyWrapCounts_TrailingNewline covers a window whose last line ends
// with '\n' at the window end: the shaper may emit a visual-line start at
// exactly len(winText) for the (empty) trailing line; it must not be
// attributed to the last real line.
func TestApplyWrapCounts_TrailingNewline(t *testing.T) {
content := "short\nlong line that wraps once here\nlast line ends with newline\n"
cb := newTestBufferForWrapWholeFile(content)
for i := 0; i < cb.WrapIndex.Len(); i++ {
cb.WrapIndex.Set(i, 7)
}
TheState = NewState()
TheState.Editor.ChunkedBuffer = cb
TheState.Editor.IMEWindowText = content
TheState.Editor.IMEWindowStartByte = 0
TheState.Editor.EditSeq = 1
// Line 0: "short" (1); line 1: wraps (2); line 2: "last line..." (1);
// plus a trailing-empty-line start at len(content).
starts := []int{0, 6, 6 + 18, len("short\nlong line that wraps once here\n"), len(content)}
fb := ui.LayoutFeedback{
GlyphLayout: ui.GlyphLayout{VisualLineStarts: starts},
WindowText: content,
WindowStartByte: 0,
WindowStartLine: 0,
EditSeq: 1,
}
TheState.applyWrapCounts(fb)
if got := cb.WrapIndex.Get(0); got != 1 {
t.Errorf("line 0 = %d, want 1", got)
}
if got := cb.WrapIndex.Get(1); got != 2 {
t.Errorf("line 1 = %d, want 2", got)
}
// Line 2 keeps the seed: the entry at len(content) belongs to the empty
// trailing line, which is already at the estimate.
if got := cb.WrapIndex.Get(2); got != 1 && got != 7 {
t.Errorf("line 2 = %d, want 1 (or untouched 7)", got)
}
}
// TestApplyWrapCounts_Guards pins the early-outs: no buffer, no wrap index,
// negative window start, empty window text, empty starts, and a window start
// byte past the window text.
func TestApplyWrapCounts_Guards(t *testing.T) {
content := "abc\ndef\n"
cb := newTestBufferForWrapWholeFile(content)
cb.WrapIndex.Set(0, 5)
cb.WrapIndex.Set(1, 5)
cb.WrapIndex.Set(2, 5)
base := func() *ui.LayoutFeedback {
return &ui.LayoutFeedback{
GlyphLayout: ui.GlyphLayout{VisualLineStarts: []int{0, 4}},
WindowText: content,
WindowStartByte: 0,
WindowStartLine: 0,
EditSeq: 0,
}
}
run := func(mut func(*State, *ui.LayoutFeedback)) {
TheState = NewState()
cbc := newTestBufferForWrapWholeFile(content)
cbc.WrapIndex.Set(0, 5)
cbc.WrapIndex.Set(1, 5)
cbc.WrapIndex.Set(2, 5)
TheState.Editor.ChunkedBuffer = cbc
TheState.Editor.IMEWindowText = content
TheState.Editor.IMEWindowStartByte = 0
fb := base()
wi := cbc.WrapIndex // capture before mut (some cases nil it)
mut(TheState, fb)
TheState.applyWrapCounts(*fb)
for i := 0; i < 3; i++ {
if got := wi.Get(i); got != 5 {
t.Errorf("guard case: line %d = %d, want untouched 5", i, got)
}
}
}
run(func(s *State, fb *ui.LayoutFeedback) { s.Editor.ChunkedBuffer = nil })
run(func(s *State, fb *ui.LayoutFeedback) { s.Editor.ChunkedBuffer.WrapIndex = nil })
run(func(s *State, fb *ui.LayoutFeedback) { fb.WindowStartLine = -1 })
run(func(s *State, fb *ui.LayoutFeedback) { fb.WindowText = "" })
run(func(s *State, fb *ui.LayoutFeedback) { fb.GlyphLayout.VisualLineStarts = nil })
_ = cb
}
// newTestBufferForWrapWholeFile builds a chunked buffer + LineIndex +
// all-ones WrapIndex over the given content (the LineIndex convention: a
// trailing "\n" opens an empty trailing line).
func newTestBufferForWrapWholeFile(content string) *ChunkedBuffer {
cb := NewChunkedBuffer("/wrap-group.txt", DefaultChunkSize, nil, "")
cb.SetContent([]byte(content))
offsets := []int32{0}
for i := 0; i < len(content); i++ {
if content[i] == '\n' {
offsets = append(offsets, int32(i+1))
}
}
cb.LineIndex = types.NewLineIndex(offsets, 0, int64(len(content)))
cb.WrapIndex = NewWrapIndex(len(offsets))
return cb
}
// byteOffsetOfLine returns the byte offset of the start of 0-based line i
// under the LineIndex convention.
func byteOffsetOfLine(content string, i int) int {
o := 0
for line := 0; line < i; line++ {
nx := strings.IndexByte(content[o:], '\n')
if nx < 0 {
return len(content)
}
o += nx + 1
}
return o
}