test: more tests
This commit is contained in:
parent
ca5a0a99a5
commit
0f0de17dff
@ -87,6 +87,113 @@ func TestDeleteCharWithCount(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
t.Run("test 'x' on empty line does nothing", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.Line(0))
|
||||
}
|
||||
if m.CursorX() != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.CursorX())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' on single character line", func(t *testing.T) {
|
||||
lines := []string{"a"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' at last char deletes it", func(t *testing.T) {
|
||||
lines := []string{"ab"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' with whitespace", func(t *testing.T) {
|
||||
lines := []string{"a b c"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "ab c" {
|
||||
t.Errorf("Line(0) = %q, want 'ab c'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' preserves other lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.LineCount())
|
||||
}
|
||||
if m.Line(1) != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.Line(1))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' multiple times deletes multiple chars", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x", "x", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "lo" {
|
||||
t.Errorf("Line(0) = %q, want 'lo'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' on line with tabs", func(t *testing.T) {
|
||||
lines := []string{"a\tb"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "ab" {
|
||||
t.Errorf("Line(0) = %q, want 'ab'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '5x' with only 3 chars available", func(t *testing.T) {
|
||||
lines := []string{"abc"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "5", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'x' in middle preserves surrounding chars", func(t *testing.T) {
|
||||
lines := []string{"abcde"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "abde" {
|
||||
t.Errorf("Line(0) = %q, want 'abde'", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteToEndOfLine(t *testing.T) {
|
||||
t.Run("test 'D' deletes to end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
@ -175,3 +282,139 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
t.Run("test 'D' on single character line", func(t *testing.T) {
|
||||
lines := []string{"a"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' at end of file", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.LineCount())
|
||||
}
|
||||
if m.Line(1) != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.Line(1))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' preserves lines above", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.Line(0))
|
||||
}
|
||||
if m.Line(2) != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.Line(2))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' cursor clamps to valid position", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.Line(0))
|
||||
}
|
||||
// Cursor should clamp to last char
|
||||
if m.CursorX() != 1 {
|
||||
t.Errorf("CursorX() = %d, want 1", m.CursorX())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' on whitespace-only line", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' from middle of whitespace-only line", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != " " {
|
||||
t.Errorf("Line(0) = %q, want ' '", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' with tabs", func(t *testing.T) {
|
||||
lines := []string{"hello\tworld"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' on line with only one char remaining after cursor", func(t *testing.T) {
|
||||
lines := []string{"ab"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' does not affect line below", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(1) != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.Line(1))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' with multiple lines", func(t *testing.T) {
|
||||
lines := []string{"first line", "second line", "third line"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "first" {
|
||||
t.Errorf("Line(0) = %q, want 'first'", m.Line(0))
|
||||
}
|
||||
if m.LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.LineCount())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'D' preserves cursor Y position", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 1})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.CursorY() != 1 {
|
||||
t.Errorf("CursorY() = %d, want 1", m.CursorY())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -574,3 +574,475 @@ func TestPasteLinewiseEdgeCases(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Charwise Paste (p) Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestPasteCharwiseBasic(t *testing.T) {
|
||||
t.Run("p pastes text after cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should insert after 'o': "helloXYZ world"
|
||||
if m.Line(0) != "helloXYZ world" {
|
||||
t.Errorf("Line(0) = %q, want 'helloXYZ world'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p at start of line pastes after first char", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hXello" {
|
||||
t.Errorf("Line(0) = %q, want 'hXello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p at end of line pastes at end", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"!"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello!" {
|
||||
t.Errorf("Line(0) = %q, want 'hello!'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{""}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"text"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "text" {
|
||||
t.Errorf("Line(0) = %q, want 'text'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p does not add new lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.LineCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPasteCharwiseWithCount(t *testing.T) {
|
||||
t.Run("2p pastes content twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hXXello" {
|
||||
t.Errorf("Line(0) = %q, want 'hXXello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("3p pastes word three times", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"start end"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 't'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"-"}),
|
||||
)
|
||||
sendKeys(tm, "3", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "start--- end" {
|
||||
t.Errorf("Line(0) = %q, want 'start--- end'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("count paste with multi-char content", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XY"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "aXYXYb" {
|
||||
t.Errorf("Line(0) = %q, want 'aXYXYb'", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPasteCharwiseCursorPosition(t *testing.T) {
|
||||
t.Run("p moves cursor to end of pasted text", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Cursor should be at position after pasted text
|
||||
if m.CursorX() != 3 {
|
||||
t.Errorf("CursorX() = %d, want 3", m.CursorX())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p cursor stays on same line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.CursorY() != 1 {
|
||||
t.Errorf("CursorY() = %d, want 1", m.CursorY())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Charwise Paste Before (P) Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestPasteBeforeCharwiseBasic(t *testing.T) {
|
||||
t.Run("P pastes text before cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}), // on space
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should insert before space: "helloXYZ world"
|
||||
if m.Line(0) != "helloXYZ world" {
|
||||
t.Errorf("Line(0) = %q, want 'helloXYZ world'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P at start of line pastes at beginning", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "Xhello" {
|
||||
t.Errorf("Line(0) = %q, want 'Xhello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P at end of line pastes before last char", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hellXo" {
|
||||
t.Errorf("Line(0) = %q, want 'hellXo'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{""}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"text"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "text" {
|
||||
t.Errorf("Line(0) = %q, want 'text'", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPasteBeforeCharwiseWithCount(t *testing.T) {
|
||||
t.Run("2P pastes content twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}), // on first 'l'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "2", "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "heXXllo" {
|
||||
t.Errorf("Line(0) = %q, want 'heXXllo'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("3P pastes word three times", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 1}), // on 'b'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"-"}),
|
||||
)
|
||||
sendKeys(tm, "3", "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "a---b" {
|
||||
t.Errorf("Line(0) = %q, want 'a---b'", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Multi-line Charwise Paste Tests (from visual mode yank)
|
||||
// =============================================================================
|
||||
|
||||
func TestPasteCharwiseMultiLine(t *testing.T) {
|
||||
t.Run("p with multi-line charwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"line1", "line2"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Current implementation errors - line should be unchanged
|
||||
if m.Line(0) != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello' (unchanged due to error)", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P with multi-line charwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"line1", "line2"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Current implementation errors - line should be unchanged
|
||||
if m.Line(0) != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello' (unchanged due to error)", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Blockwise Paste Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestPasteBlockwiseBasic(t *testing.T) {
|
||||
t.Run("p with blockwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"aaaa", "bbbb", "cccc"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.BlockwiseRegister, []string{"XX", "YY", "ZZ"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Current implementation errors - lines should be unchanged
|
||||
if m.Line(0) != "aaaa" {
|
||||
t.Errorf("Line(0) = %q, want 'aaaa' (unchanged due to error)", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P with blockwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"aaaa", "bbbb", "cccc"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.BlockwiseRegister, []string{"XX", "YY", "ZZ"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Current implementation errors - lines should be unchanged
|
||||
if m.Line(0) != "aaaa" {
|
||||
t.Errorf("Line(0) = %q, want 'aaaa' (unchanged due to error)", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Charwise Paste Edge Cases
|
||||
// =============================================================================
|
||||
|
||||
func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("p with empty charwise register does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p with empty string in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{""}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p preserves special characters", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"\t"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "a\tb" {
|
||||
t.Errorf("Line(0) = %q, want 'a\\tb'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p with spaces", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{" "}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "a b" {
|
||||
t.Errorf("Line(0) = %q, want 'a b'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p on line with only whitespace", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{" "}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != " X " {
|
||||
t.Errorf("Line(0) = %q, want ' X '", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P with empty charwise register does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("large count paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "1", "0", "p") // 10p
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "aXXXXXXXXXXb" {
|
||||
t.Errorf("Line(0) = %q, want 'aXXXXXXXXXXb'", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Integration: Yank then Paste
|
||||
// =============================================================================
|
||||
|
||||
func TestYankThenPasteCharwise(t *testing.T) {
|
||||
t.Run("yw then p pastes yanked word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "w") // yank "hello "
|
||||
sendKeys(tm, "$") // go to end
|
||||
sendKeys(tm, "p") // paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello worldhello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ye then p pastes yanked word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "e") // yank "hello"
|
||||
sendKeys(tm, "$") // go to end
|
||||
sendKeys(tm, "p") // paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual select then y then p", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "y") // select and yank "hel"
|
||||
sendKeys(tm, "$") // go to end
|
||||
sendKeys(tm, "p") // paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello worldhel" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhel'", m.Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1038,3 +1038,249 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Visual Yank → Paste Round-Trip Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual charwise yank then paste single line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// Select "hello", yank it, move to end, paste
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "y", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual charwise yank then paste before", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
// Select "world", yank it, go to start, paste before
|
||||
sendKeys(tm, "v", "$", "y", "0", "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "worldhello world" {
|
||||
t.Errorf("Line(0) = %q, want 'worldhello world'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual line yank then paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// V yank line 1, go to line 2, paste
|
||||
sendKeys(tm, "V", "y", "j", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.LineCount())
|
||||
}
|
||||
if m.Line(2) != "line 1" {
|
||||
t.Errorf("Line(2) = %q, want 'line 1'", m.Line(2))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual line yank multiple lines then paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3", "line 4"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// V select lines 1-2, yank, go to end, paste
|
||||
sendKeys(tm, "V", "j", "y", "G", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 6 {
|
||||
t.Errorf("LineCount() = %d, want 6", m.LineCount())
|
||||
}
|
||||
if m.Line(4) != "line 1" {
|
||||
t.Errorf("Line(4) = %q, want 'line 1'", m.Line(4))
|
||||
}
|
||||
if m.Line(5) != "line 2" {
|
||||
t.Errorf("Line(5) = %q, want 'line 2'", m.Line(5))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual line yank then paste before", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
// V yank line 3, go to line 1, paste before
|
||||
sendKeys(tm, "V", "y", "g", "g", "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.LineCount())
|
||||
}
|
||||
if m.Line(0) != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.Line(0))
|
||||
}
|
||||
if m.Line(1) != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.Line(1))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("yy then p duplicates line below", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original", "other"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.LineCount())
|
||||
}
|
||||
if m.Line(0) != "original" {
|
||||
t.Errorf("Line(0) = %q, want 'original'", m.Line(0))
|
||||
}
|
||||
if m.Line(1) != "original" {
|
||||
t.Errorf("Line(1) = %q, want 'original'", m.Line(1))
|
||||
}
|
||||
if m.Line(2) != "other" {
|
||||
t.Errorf("Line(2) = %q, want 'other'", m.Line(2))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("yy then P duplicates line above", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original", "other"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y", "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.LineCount())
|
||||
}
|
||||
if m.Line(0) != "original" {
|
||||
t.Errorf("Line(0) = %q, want 'original'", m.Line(0))
|
||||
}
|
||||
if m.Line(1) != "other" {
|
||||
t.Errorf("Line(1) = %q, want 'other'", m.Line(1))
|
||||
}
|
||||
if m.Line(2) != "other" {
|
||||
t.Errorf("Line(2) = %q, want 'other'", m.Line(2))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("yw then p pastes word after cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// yw yanks "hello ", move to end of world, paste
|
||||
sendKeys(tm, "y", "w", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello worldhello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ye then p pastes word after cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// ye yanks "hello" (inclusive), move to end of line, paste
|
||||
sendKeys(tm, "y", "e", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual select partial word yank then paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"abcdefgh"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}), // on 'c'
|
||||
)
|
||||
// Select "cde", yank, go to end, paste
|
||||
sendKeys(tm, "v", "l", "l", "y", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Line(0) != "abcdefghcde" {
|
||||
t.Errorf("Line(0) = %q, want 'abcdefghcde'", m.Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual yank empty selection does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
)
|
||||
// Enter visual mode then immediately yank (single char)
|
||||
sendKeys(tm, "v", "y")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
reg, ok := m.GetRegister('"')
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
// Should have yanked single char 'l'
|
||||
if len(reg.Content) != 1 || reg.Content[0] != "l" {
|
||||
t.Errorf("register content = %q, want 'l'", reg.Content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dd then p moves deleted line down", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// dd deletes line 1, p pastes it below cursor (now on line 2)
|
||||
sendKeys(tm, "d", "d", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.LineCount())
|
||||
}
|
||||
if m.Line(0) != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.Line(0))
|
||||
}
|
||||
if m.Line(1) != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.Line(1))
|
||||
}
|
||||
if m.Line(2) != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.Line(2))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("2yy then 2p pastes twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// 2yy yanks lines 1-2, 2p pastes them twice after current line
|
||||
sendKeys(tm, "2", "y", "y", "2", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.LineCount() != 7 {
|
||||
t.Errorf("LineCount() = %d, want 7", m.LineCount())
|
||||
}
|
||||
// Original + 2 copies of 2 lines = 3 + 4 = 7
|
||||
if m.Line(1) != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.Line(1))
|
||||
}
|
||||
if m.Line(2) != "line 2" {
|
||||
t.Errorf("Line(2) = %q, want 'line 2'", m.Line(2))
|
||||
}
|
||||
if m.Line(3) != "line 1" {
|
||||
t.Errorf("Line(3) = %q, want 'line 1'", m.Line(3))
|
||||
}
|
||||
if m.Line(4) != "line 2" {
|
||||
t.Errorf("Line(4) = %q, want 'line 2'", m.Line(4))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user