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:
parent
4dedb15a36
commit
066b817200
@ -6,6 +6,19 @@ import (
|
|||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"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
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user