package editor import ( "testing" "git.gophernest.net/azpect/TextEditor/internal/core" ) func TestJoinLines(t *testing.T) { t.Run("J joins current line with next line using a space", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"hello", "world"}) sendKeys(tm, "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"hello world"}) if m.ActiveWindow().Cursor.Line != 0 { t.Errorf("Cursor.Line = %d, want 0", m.ActiveWindow().Cursor.Line) } if m.ActiveWindow().Cursor.Col != 0 { t.Errorf("Cursor.Col = %d, want 0", m.ActiveWindow().Cursor.Col) } }) t.Run("J joins from the cursor line, not always the first line", func(t *testing.T) { tm := newTestModelWithLinesAndCursorPos(t, []string{"one", "two", "three"}, core.Position{Line: 1, Col: 2}) sendKeys(tm, "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"one", "two three"}) if m.ActiveWindow().Cursor.Line != 1 { t.Errorf("Cursor.Line = %d, want 1", m.ActiveWindow().Cursor.Line) } if m.ActiveWindow().Cursor.Col != 2 { t.Errorf("Cursor.Col = %d, want 2", m.ActiveWindow().Cursor.Col) } }) t.Run("J trims indentation from the joined line", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"foo", " bar"}) sendKeys(tm, "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"foo bar"}) }) } func TestJoinLinesNoSpace(t *testing.T) { t.Run("gJ joins without inserting a space", func(t *testing.T) { tm := newTestModelWithLinesAndCursorPos(t, []string{"hello", "world"}, core.Position{Line: 0, Col: 3}) sendKeys(tm, "g", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"helloworld"}) if m.ActiveWindow().Cursor.Line != 0 { t.Errorf("Cursor.Line = %d, want 0", m.ActiveWindow().Cursor.Line) } if m.ActiveWindow().Cursor.Col != 3 { t.Errorf("Cursor.Col = %d, want 3", m.ActiveWindow().Cursor.Col) } }) t.Run("gJ preserves leading whitespace on the next line", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"foo", " bar"}) sendKeys(tm, "g", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"foo bar"}) }) } func TestJoinLinesWithCount(t *testing.T) { t.Run("3J joins three lines with spaces", func(t *testing.T) { tm := newTestModelWithLinesAndCursorPos(t, []string{"a", "b", "c", "d"}, core.Position{Line: 0, Col: 1}) sendKeys(tm, "3", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"a b c", "d"}) if m.ActiveWindow().Cursor.Line != 0 { t.Errorf("Cursor.Line = %d, want 0", m.ActiveWindow().Cursor.Line) } if m.ActiveWindow().Cursor.Col != 1 { t.Errorf("Cursor.Col = %d, want 1", m.ActiveWindow().Cursor.Col) } }) t.Run("3gJ joins three lines without spaces", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"a", "b", "c", "d"}) sendKeys(tm, "3", "g", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"abc", "d"}) }) t.Run("count larger than remaining lines joins through end of file", func(t *testing.T) { tm := newTestModelWithLinesAndCursorPos(t, []string{"a", "b", "c"}, core.Position{Line: 1, Col: 0}) sendKeys(tm, "9", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"a", "b c"}) }) } func TestJoinLinesEdgeCases(t *testing.T) { t.Run("J on last line does nothing", func(t *testing.T) { tm := newTestModelWithLinesAndCursorPos(t, []string{"one", "two"}, core.Position{Line: 1, Col: 2}) sendKeys(tm, "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"one", "two"}) if m.ActiveWindow().Cursor.Line != 1 { t.Errorf("Cursor.Line = %d, want 1", m.ActiveWindow().Cursor.Line) } if m.ActiveWindow().Cursor.Col != 2 { t.Errorf("Cursor.Col = %d, want 2", m.ActiveWindow().Cursor.Col) } }) t.Run("gJ on single-line buffer does nothing", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"solo"}) sendKeys(tm, "g", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"solo"}) }) t.Run("J across many empty lines keeps a single separating space", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"foo", "", "", "", "bar"}) sendKeys(tm, "5", "J") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"foo bar"}) }) } func TestJoinLinesRepeatAndUndo(t *testing.T) { t.Run("dot repeats J join", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"a", "b", "c", "d"}) sendKeys(tm, "J", "j", ".") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"a b", "c d"}) }) t.Run("dot repeats gJ join", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"a", "b", "c", "d"}) sendKeys(tm, "g", "J", "j", ".") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"ab", "cd"}) }) t.Run("undo restores lines after J", func(t *testing.T) { tm := newTestModelWithLines(t, []string{"left", "right"}) sendKeys(tm, "J", "u") m := getFinalModel(t, tm) assertBufferLines(t, m.ActiveBuffer(), []string{"left", "right"}) }) } func assertBufferLines(t *testing.T, buf *core.Buffer, want []string) { t.Helper() got := make([]string, buf.LineCount()) for i := 0; i < buf.LineCount(); i++ { got[i] = buf.Line(i) } if len(got) != len(want) { t.Errorf("lines = %q (len=%d), want %q (len=%d)", got, len(got), want, len(want)) return } for i := range got { if got[i] != want[i] { t.Errorf("lines = %q (len=%d), want %q (len=%d)", got, len(got), want, len(want)) return } } }