From 477fa70fd4c613f0d1eaf6b73de5e477431e0577 Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Thu, 29 Feb 2024 13:59:50 +1100 Subject: [PATCH] Improve textarea tests Improve textarea tests to check the cursor location. This is the displayed location and not the line row and column (which does not take into account soft wrapping). As part of this improvement, a number of existing tests were merged into the view test tables as it was unnecessary duplication of code having them separated. Signed-off-by: Michael Lorant --- textarea/textarea_test.go | 455 ++++++++++++++++++++++---------------- 1 file changed, 268 insertions(+), 187 deletions(-) diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index d37a74db..e02ecf8f 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -10,101 +10,6 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -func TestNew(t *testing.T) { - textarea := newTextArea() - view := textarea.View() - - if !strings.Contains(view, ">") { - t.Log(view) - t.Error("Text area did not render the prompt") - } - - if !strings.Contains(view, "World!") { - t.Log(view) - t.Error("Text area did not render the placeholder") - } -} - -func TestInput(t *testing.T) { - textarea := newTextArea() - - input := "foo" - - for _, k := range []rune(input) { - textarea, _ = textarea.Update(keyPress(k)) - } - - view := textarea.View() - - if !strings.Contains(view, input) { - t.Log(view) - t.Error("Text area did not render the input") - } - - if textarea.col != len(input) { - t.Log(view) - t.Error("Text area did not move the cursor to the correct position") - } -} - -func TestSoftWrap(t *testing.T) { - textarea := newTextArea() - textarea.Prompt = "" - textarea.ShowLineNumbers = false - textarea.SetWidth(5) - textarea.SetHeight(5) - textarea.CharLimit = 60 - - textarea, _ = textarea.Update(nil) - - input := "foo bar baz" - - for _, k := range []rune(input) { - textarea, _ = textarea.Update(keyPress(k)) - } - - view := textarea.View() - - for _, word := range strings.Split(input, " ") { - if !strings.Contains(view, word) { - t.Log(view) - t.Error("Text area did not render the input") - } - } - - // Due to the word wrapping, each word will be on a new line and the - // text area will look like this: - // - // > foo - // > bar - // > baz█ - // - // However, due to soft-wrapping the column will still be at the end of the line. - if textarea.row != 0 || textarea.col != len(input) { - t.Log(view) - t.Error("Text area did not move the cursor to the correct position") - } -} - -func TestCharLimit(t *testing.T) { - textarea := newTextArea() - - // First input (foo bar) should be accepted as it will fall within the - // CharLimit. Second input (baz) should not appear in the input. - input := []string{"foo bar", "baz"} - textarea.CharLimit = len(input[0]) - - for _, k := range []rune(strings.Join(input, " ")) { - textarea, _ = textarea.Update(keyPress(k)) - } - - view := textarea.View() - if strings.Contains(view, input[1]) { - t.Log(view) - t.Error("Text area should not include input past the character limit") - } -} - func TestVerticalScrolling(t *testing.T) { textarea := newTextArea() textarea.Prompt = "" @@ -420,21 +325,29 @@ func TestVerticalNavigationShouldRememberPositionWhileTraversing(t *testing.T) { } func TestView(t *testing.T) { + type want struct { + view string + cursorRow int + cursorCol int + } + tests := []struct { name string modelFunc func(Model) Model - expected string + want want }{ { name: "placeholder", - expected: heredoc.Doc(` - > 1 Hello, World! - > ~ - > ~ - > ~ - > ~ - > ~ - `), + want: want{ + view: heredoc.Doc(` + > 1 Hello, World! + > ~ + > ~ + > ~ + > ~ + > ~ + `), + }, }, { name: "single line", @@ -443,14 +356,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > 1 the first line - > ~ - > ~ - > ~ - > ~ - > ~ - `), + want: want{ + view: heredoc.Doc(` + > 1 the first line + > ~ + > ~ + > ~ + > ~ + > ~ + `), + cursorRow: 0, + cursorCol: 14, + }, }, { name: "multiple lines", @@ -459,14 +376,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > 1 the first line - > 2 the second line - > 3 the third line - > ~ - > ~ - > ~ - `), + want: want{ + view: heredoc.Doc(` + > 1 the first line + > 2 the second line + > 3 the third line + > ~ + > ~ + > ~ + `), + cursorRow: 2, + cursorCol: 14, + }, }, { name: "single line without line numbers", @@ -476,14 +397,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > the first line - > ~ - > ~ - > ~ - > ~ - > ~ - `), + want: want{ + view: heredoc.Doc(` + > the first line + > ~ + > ~ + > ~ + > ~ + > ~ + `), + cursorRow: 0, + cursorCol: 14, + }, }, { name: "multipline lines without line numbers", @@ -493,14 +418,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > the first line - > the second line - > the third line - > ~ - > ~ - > ~ - `), + want: want{ + view: heredoc.Doc(` + > the first line + > the second line + > the third line + > ~ + > ~ + > ~ + `), + cursorRow: 2, + cursorCol: 14, + }, }, { name: "single line and custom end of buffer character", @@ -510,14 +439,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > 1 the first line - > * - > * - > * - > * - > * - `), + want: want{ + view: heredoc.Doc(` + > 1 the first line + > * + > * + > * + > * + > * + `), + cursorRow: 0, + cursorCol: 14, + }, }, { name: "multiple lines and custom end of buffer character", @@ -527,14 +460,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > 1 the first line - > 2 the second line - > 3 the third line - > * - > * - > * - `), + want: want{ + view: heredoc.Doc(` + > 1 the first line + > 2 the second line + > 3 the third line + > * + > * + > * + `), + cursorRow: 2, + cursorCol: 14, + }, }, { name: "single line without line numbers and custom end of buffer character", @@ -545,14 +482,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > the first line - > * - > * - > * - > * - > * - `), + want: want{ + view: heredoc.Doc(` + > the first line + > * + > * + > * + > * + > * + `), + cursorRow: 0, + cursorCol: 14, + }, }, { name: "multiple lines without line numbers and custom end of buffer character", @@ -563,14 +504,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - > the first line - > the second line - > the third line - > * - > * - > * - `), + want: want{ + view: heredoc.Doc(` + > the first line + > the second line + > the third line + > * + > * + > * + `), + cursorRow: 2, + cursorCol: 14, + }, }, { name: "single line and custom prompt", @@ -580,14 +525,18 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - * 1 the first line - * ~ - * ~ - * ~ - * ~ - * ~ - `), + want: want{ + view: heredoc.Doc(` + * 1 the first line + * ~ + * ~ + * ~ + * ~ + * ~ + `), + cursorRow: 0, + cursorCol: 14, + }, }, { name: "multiple lines and custom prompt", @@ -597,14 +546,131 @@ func TestView(t *testing.T) { return m }, - expected: heredoc.Doc(` - * 1 the first line - * 2 the second line - * 3 the third line - * ~ - * ~ - * ~ - `), + want: want{ + view: heredoc.Doc(` + * 1 the first line + * 2 the second line + * 3 the third line + * ~ + * ~ + * ~ + `), + cursorRow: 2, + cursorCol: 14, + }, + }, + { + name: "type single line", + modelFunc: func(m Model) Model { + input := "foo" + m = sendString(m, input) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 foo + > ~ + > ~ + > ~ + > ~ + > ~ + `), + cursorRow: 0, + cursorCol: 3, + }, + }, + { + name: "type multiple lines", + modelFunc: func(m Model) Model { + input := "foo\nbar\nbaz" + m = sendString(m, input) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 foo + > 2 bar + > 3 baz + > ~ + > ~ + > ~ + `), + cursorRow: 2, + cursorCol: 3, + }, + }, + { + name: "softwrap", + modelFunc: func(m Model) Model { + m.ShowLineNumbers = false + m.Prompt = "" + m.SetWidth(5) + + input := "foo bar baz" + m = sendString(m, input) + + return m + }, + want: want{ + view: heredoc.Doc(` + foo + bar + baz + ~ + ~ + ~ + `), + cursorRow: 2, + cursorCol: 3, + }, + }, + { + name: "single line character limit", + modelFunc: func(m Model) Model { + m.CharLimit = 7 + + input := "foo bar baz" + m = sendString(m, input) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 foo bar + > ~ + > ~ + > ~ + > ~ + > ~ + `), + cursorRow: 0, + cursorCol: 7, + }, + }, + { + name: "multiple lines character limit", + modelFunc: func(m Model) Model { + m.CharLimit = 19 + + input := "foo bar baz\nfoo bar baz" + m = sendString(m, input) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 foo bar baz + > 2 foo bar + > ~ + > ~ + > ~ + > ~ + `), + cursorRow: 1, + cursorCol: 7, + }, }, } @@ -617,10 +683,17 @@ func TestView(t *testing.T) { } view := stripString(textarea.View()) - expected := stripString(tt.expected) + wantView := stripString(tt.want.view) + + if view != wantView { + t.Fatalf("Want:\n%v\nGot:\n%v\n", wantView, view) + } - if view != expected { - t.Fatalf("Expected:\n%v\nGot:\n%v\n", expected, view) + cursorRow := textarea.cursorLineNumber() + cursorCol := textarea.LineInfo().ColumnOffset + if tt.want.cursorRow != cursorRow || tt.want.cursorCol != cursorCol { + format := "Want cursor at row: %v, col: %v Got: row: %v col: %v\n" + t.Fatalf(format, tt.want.cursorRow, tt.want.cursorCol, cursorRow, cursorCol) } }) } @@ -643,6 +716,14 @@ func keyPress(key rune) tea.Msg { return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{key}, Alt: false} } +func sendString(m Model, str string) Model { + for _, k := range []rune(str) { + m, _ = m.Update(keyPress(k)) + } + + return m +} + func stripString(str string) string { s := stripansi.Strip(str) ss := strings.Split(s, "\n")