test: added some more tests to confirm the undo tree is "good"

Yay! These are from Sonnet 4.0, hope theyre good.
This commit is contained in:
Hayden Hargreaves 2026-03-30 23:10:18 -07:00
parent 4dedb15a36
commit 066b817200

View File

@ -6,6 +6,19 @@ import (
"git.gophernest.net/azpect/TextEditor/internal/core"
)
// equalStringSlices compares two string slices for equality
func equalStringSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// ============================================================================
// BASIC UNDO/REDO TESTS
// ============================================================================
@ -747,3 +760,233 @@ func TestUndoComplexScenarios(t *testing.T) {
}
})
}
// =================================================================
// PASTE OPERATIONS TESTS
// =================================================================
func TestUndoPasteOperations(t *testing.T) {
t.Run("basic p (paste after) undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"line1", "line2"})
// Yank first line and paste after second line
sendKeys(tm, "y", "y") // yank current line (line1)
sendKeys(tm, "j") // move to line2
sendKeys(tm, "p") // paste after line2
sendKeys(tm, "u") // undo paste
m := getFinalModel(t, tm)
expected := []string{"line1", "line2"}
if len(m.ActiveBuffer().Lines) != len(expected) {
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
}
for i, exp := range expected {
if m.ActiveBuffer().Lines[i] != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], exp)
}
}
// Cursor should be back at line2
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("Cursor.Line = %d, want 1", m.ActiveWindow().Cursor.Line)
}
})
t.Run("basic P (paste before) undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"line1", "line2"})
// Yank first line and paste before second line
sendKeys(tm, "y", "y") // yank current line (line1)
sendKeys(tm, "j") // move to line2
sendKeys(tm, "P") // paste before line2
sendKeys(tm, "u") // undo paste
m := getFinalModel(t, tm)
expected := []string{"line1", "line2"}
if len(m.ActiveBuffer().Lines) != len(expected) {
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
}
for i, exp := range expected {
if m.ActiveBuffer().Lines[i] != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], exp)
}
}
// Cursor should be back at line2
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("Cursor.Line = %d, want 1", m.ActiveWindow().Cursor.Line)
}
})
t.Run("charwise paste undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"hello world"})
// Yank "hello" and paste after "world"
sendKeys(tm, "y", "w") // yank word "hello"
sendKeys(tm, "$") // move to end
sendKeys(tm, "p") // paste after cursor
sendKeys(tm, "u") // undo paste
m := getFinalModel(t, tm)
expected := []string{"hello world"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
t.Run("visual mode paste undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"hello world", "foo bar"})
// Yank "hello" then select "world" and paste over it
sendKeys(tm, "y", "w") // yank "hello"
sendKeys(tm, "w") // move to "world"
sendKeys(tm, "v", "e") // select "world"
sendKeys(tm, "p") // paste over selection
sendKeys(tm, "u") // undo paste
m := getFinalModel(t, tm)
expected := []string{"hello world", "foo bar"}
if len(m.ActiveBuffer().Lines) != len(expected) {
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
}
for i, exp := range expected {
if m.ActiveBuffer().Lines[i] != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], exp)
}
}
})
t.Run("multiple paste operations undo separately", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"base"})
sendKeys(tm, "y", "y") // yank "base"
sendKeys(tm, "p") // paste: "base\nbase"
sendKeys(tm, "p") // paste: "base\nbase\nbase"
sendKeys(tm, "u") // undo last paste: "base\nbase"
sendKeys(tm, "u") // undo first paste: "base"
m := getFinalModel(t, tm)
expected := []string{"base"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
t.Run("paste with count undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"test"})
sendKeys(tm, "y", "y") // yank "test"
sendKeyString(tm, "3p") // paste 3 times
sendKeys(tm, "u") // undo (should undo all 3 pastes as one block)
m := getFinalModel(t, tm)
expected := []string{"test"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
}
// =================================================================
// COMPLEX COUNT OPERATIONS TESTS
// =================================================================
func TestUndoComplexCountOperations(t *testing.T) {
t.Run("5dd undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"1", "2", "3", "4", "5", "6", "7"})
sendKeys(tm, "j", "j") // move to line 3
sendKeyString(tm, "5dd") // delete 5 lines (3,4,5,6,7)
sendKeys(tm, "u") // undo
m := getFinalModel(t, tm)
expected := []string{"1", "2", "3", "4", "5", "6", "7"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
// Cursor should be back at line 3 (index 2)
if m.ActiveWindow().Cursor.Line != 2 {
t.Errorf("Cursor.Line = %d, want 2", m.ActiveWindow().Cursor.Line)
}
})
t.Run("3cw (change 3 words) undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"one two three four five"})
sendKeys(tm, "3", "c", "w") // change 3 words
sendKeys(tm, "CHANGED") // type replacement
sendKeys(tm, "esc") // exit insert mode
sendKeys(tm, "u") // undo
m := getFinalModel(t, tm)
expected := []string{"one two three four five"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
t.Run("10x (delete 10 chars) undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"abcdefghijklmnopqrstuvwxyz"})
sendKeys(tm, "5", "|") // move to column 5 (f)
sendKeyString(tm, "10x") // delete 10 chars (fghijklmno)
sendKeys(tm, "u") // undo
m := getFinalModel(t, tm)
expected := []string{"abcdefghijklmnopqrstuvwxyz"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
// Cursor should be back at column 4 (index of 'e', 0-based)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("Cursor.Col = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("2cc (change 2 lines) undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"line1", "line2", "line3", "line4"})
sendKeys(tm, "j") // move to line2
sendKeys(tm, "2", "c", "c") // change 2 lines (line2, line3)
sendKeys(tm, "NEW", "LINE") // type replacement
sendKeys(tm, "esc") // exit insert mode
sendKeys(tm, "u") // undo
m := getFinalModel(t, tm)
expected := []string{"line1", "line2", "line3", "line4"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
t.Run("4diw (delete 4 words) undo", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"word1 word2 word3 word4 word5"})
sendKeys(tm, "w") // move to word2
sendKeyString(tm, "4diw") // delete 4 words (word2, word3, word4, word5)
sendKeys(tm, "u") // undo
m := getFinalModel(t, tm)
expected := []string{"word1 word2 word3 word4 word5"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
t.Run("complex count with paste: 3p after 2yy", func(t *testing.T) {
tm := newTestModelWithLines(t, []string{"A", "B", "C", "D"})
sendKeyString(tm, "2yy") // yank 2 lines (A, B)
sendKeys(tm, "j", "j") // move to line C
sendKeyString(tm, "3p") // paste 3 times
sendKeys(tm, "u") // undo paste
m := getFinalModel(t, tm)
expected := []string{"A", "B", "C", "D"}
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
// Cursor should be back at line C (index 2)
if m.ActiveWindow().Cursor.Line != 2 {
t.Errorf("Cursor.Line = %d, want 2", m.ActiveWindow().Cursor.Line)
}
})
}