mirror of
https://github.com/zoriya/bubbles.git
synced 2026-08-04 20:26:08 +00:00
2016 lines
39 KiB
Go
2016 lines
39 KiB
Go
package textarea
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"unicode"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
tea "charm.land/bubbletea/v2"
|
|
"charm.land/lipgloss/v2"
|
|
"github.com/charmbracelet/x/ansi"
|
|
)
|
|
|
|
func TestVerticalScrolling(t *testing.T) {
|
|
textarea := newTextArea()
|
|
textarea.Prompt = ""
|
|
textarea.ShowLineNumbers = false
|
|
textarea.SetHeight(1)
|
|
textarea.SetWidth(20)
|
|
textarea.CharLimit = 100
|
|
|
|
textarea, _ = textarea.Update(nil)
|
|
|
|
input := "This is a really long line that should wrap around the text area."
|
|
|
|
for _, k := range input {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
}
|
|
|
|
view := textarea.View()
|
|
|
|
// The view should contain the end of "line" of the input.
|
|
if !strings.Contains(view, "the text area.") {
|
|
t.Log(view)
|
|
t.Error("Text area did not render the input")
|
|
}
|
|
|
|
// But we should be able to scroll to see the next line.
|
|
// Let's scroll down for each line to view the full input.
|
|
lines := []string{
|
|
"This is a really",
|
|
"long line that",
|
|
"should wrap around",
|
|
"the text area.",
|
|
}
|
|
textarea.viewport.GotoTop()
|
|
for _, line := range lines {
|
|
view = textarea.View()
|
|
if !strings.Contains(view, line) {
|
|
t.Log(view)
|
|
t.Error("Text area did not render the correct scrolled input")
|
|
}
|
|
textarea.viewport.ScrollDown(1)
|
|
}
|
|
}
|
|
|
|
func TestWordWrapOverflowing(t *testing.T) {
|
|
// An interesting edge case is when the user enters many words that fill up
|
|
// the text area and then goes back up and inserts a few words which causes
|
|
// a cascading wrap and causes an overflow of the last line.
|
|
//
|
|
// In this case, we should not let the user insert more words if, after the
|
|
// entire wrap is complete, the last line is overflowing.
|
|
textarea := newTextArea()
|
|
|
|
textarea.SetHeight(3)
|
|
textarea.SetWidth(20)
|
|
textarea.CharLimit = 500
|
|
|
|
textarea, _ = textarea.Update(nil)
|
|
|
|
input := "Testing Testing Testing Testing Testing Testing Testing Testing"
|
|
|
|
for _, k := range input {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
textarea.View()
|
|
}
|
|
|
|
// We have essentially filled the text area with input.
|
|
// Let's see if we can cause wrapping to overflow the last line.
|
|
textarea.row = 0
|
|
textarea.col = 0
|
|
|
|
input = "Testing"
|
|
|
|
for _, k := range input {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
textarea.View()
|
|
}
|
|
|
|
lastLineWidth := textarea.LineInfo().Width
|
|
if lastLineWidth > 20 {
|
|
t.Log(lastLineWidth)
|
|
t.Log(textarea.View())
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
func TestValueSoftWrap(t *testing.T) {
|
|
textarea := newTextArea()
|
|
textarea.SetWidth(16)
|
|
textarea.SetHeight(10)
|
|
textarea.CharLimit = 500
|
|
|
|
textarea, _ = textarea.Update(nil)
|
|
|
|
input := "Testing Testing Testing Testing Testing Testing Testing Testing"
|
|
|
|
for _, k := range []rune(input) {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
textarea.View()
|
|
}
|
|
|
|
value := textarea.Value()
|
|
if value != input {
|
|
t.Log(value)
|
|
t.Log(input)
|
|
t.Fatal("The text area does not have the correct value")
|
|
}
|
|
}
|
|
|
|
func TestSetValue(t *testing.T) {
|
|
textarea := newTextArea()
|
|
textarea.SetValue(strings.Join([]string{"Foo", "Bar", "Baz"}, "\n"))
|
|
|
|
if textarea.row != 2 && textarea.col != 3 {
|
|
t.Log(textarea.row, textarea.col)
|
|
t.Fatal("Cursor Should be on row 2 column 3 after inserting 2 new lines")
|
|
}
|
|
|
|
value := textarea.Value()
|
|
if value != "Foo\nBar\nBaz" {
|
|
t.Fatal("Value should be Foo\nBar\nBaz")
|
|
}
|
|
|
|
// SetValue should reset text area
|
|
textarea.SetValue("Test")
|
|
value = textarea.Value()
|
|
if value != "Test" {
|
|
t.Log(value)
|
|
t.Fatal("Text area was not reset when SetValue() was called")
|
|
}
|
|
}
|
|
|
|
func TestInsertString(t *testing.T) {
|
|
textarea := newTextArea()
|
|
|
|
// Insert some text
|
|
input := "foo baz"
|
|
|
|
for _, k := range []rune(input) {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
}
|
|
|
|
// Put cursor in the middle of the text
|
|
textarea.col = 4
|
|
|
|
textarea.InsertString("bar ")
|
|
|
|
value := textarea.Value()
|
|
if value != "foo bar baz" {
|
|
t.Log(value)
|
|
t.Fatal("Expected insert string to insert bar between foo and baz")
|
|
}
|
|
}
|
|
|
|
func TestCanHandleEmoji(t *testing.T) {
|
|
textarea := newTextArea()
|
|
input := "🧋"
|
|
|
|
for _, k := range []rune(input) {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
}
|
|
|
|
value := textarea.Value()
|
|
if value != input {
|
|
t.Log(value)
|
|
t.Fatal("Expected emoji to be inserted")
|
|
}
|
|
|
|
input = "🧋🧋🧋"
|
|
|
|
textarea.SetValue(input)
|
|
|
|
value = textarea.Value()
|
|
if value != input {
|
|
t.Log(value)
|
|
t.Fatal("Expected emoji to be inserted")
|
|
}
|
|
|
|
if textarea.col != 3 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the third character")
|
|
}
|
|
|
|
if charOffset := textarea.LineInfo().CharOffset; charOffset != 6 {
|
|
t.Log(charOffset)
|
|
t.Fatal("Expected cursor to be on the sixth character")
|
|
}
|
|
}
|
|
|
|
func TestVerticalNavigationKeepsCursorHorizontalPosition(t *testing.T) {
|
|
textarea := newTextArea()
|
|
textarea.SetWidth(20)
|
|
|
|
textarea.SetValue(strings.Join([]string{"你好你好", "Hello"}, "\n"))
|
|
|
|
textarea.row = 0
|
|
textarea.col = 2
|
|
|
|
// 你好|你好
|
|
// Hell|o
|
|
// 1234|
|
|
|
|
// Let's imagine our cursor is on the first line where the pipe is.
|
|
// We press the down arrow to get to the next line.
|
|
// The issue is that if we keep the cursor on the same column, the cursor will jump to after the `e`.
|
|
//
|
|
// 你好|你好
|
|
// He|llo
|
|
//
|
|
// But this is wrong because visually we were at the 4th character due to
|
|
// the first line containing double-width runes.
|
|
// We want to keep the cursor on the same visual column.
|
|
//
|
|
// 你好|你好
|
|
// Hell|o
|
|
//
|
|
// This test ensures that the cursor is kept on the same visual column by
|
|
// ensuring that the column offset goes from 2 -> 4.
|
|
|
|
lineInfo := textarea.LineInfo()
|
|
if lineInfo.CharOffset != 4 || lineInfo.ColumnOffset != 2 {
|
|
t.Log(lineInfo.CharOffset)
|
|
t.Log(lineInfo.ColumnOffset)
|
|
t.Fatal("Expected cursor to be on the fourth character because there are two double width runes on the first line.")
|
|
}
|
|
|
|
downMsg := tea.KeyPressMsg{Code: tea.KeyDown}
|
|
textarea, _ = textarea.Update(downMsg)
|
|
|
|
lineInfo = textarea.LineInfo()
|
|
if lineInfo.CharOffset != 4 || lineInfo.ColumnOffset != 4 {
|
|
t.Log(lineInfo.CharOffset)
|
|
t.Log(lineInfo.ColumnOffset)
|
|
t.Fatal("Expected cursor to be on the fourth character because we came down from the first line.")
|
|
}
|
|
}
|
|
|
|
func TestVerticalNavigationShouldRememberPositionWhileTraversing(t *testing.T) {
|
|
textarea := newTextArea()
|
|
textarea.SetWidth(40)
|
|
|
|
// Let's imagine we have a text area with the following content:
|
|
//
|
|
// Hello
|
|
// World
|
|
// This is a long line.
|
|
//
|
|
// If we are at the end of the last line and go up, we should be at the end
|
|
// of the second line.
|
|
// And, if we go up again we should be at the end of the first line.
|
|
// But, if we go back down twice, we should be at the end of the last line
|
|
// again and not the fifth (length of second line) character of the last line.
|
|
//
|
|
// In other words, we should remember the last horizontal position while
|
|
// traversing vertically.
|
|
|
|
textarea.SetValue(strings.Join([]string{"Hello", "World", "This is a long line."}, "\n"))
|
|
|
|
// We are at the end of the last line.
|
|
if textarea.col != 20 || textarea.row != 2 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the 20th character of the last line")
|
|
}
|
|
|
|
// Let's go up.
|
|
upMsg := tea.KeyPressMsg{Code: tea.KeyUp}
|
|
textarea, _ = textarea.Update(upMsg)
|
|
|
|
// We should be at the end of the second line.
|
|
if textarea.col != 5 || textarea.row != 1 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the 5th character of the second line")
|
|
}
|
|
|
|
// And, again.
|
|
textarea, _ = textarea.Update(upMsg)
|
|
|
|
// We should be at the end of the first line.
|
|
if textarea.col != 5 || textarea.row != 0 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the 5th character of the first line")
|
|
}
|
|
|
|
// Let's go down, twice.
|
|
downMsg := tea.KeyPressMsg{Code: tea.KeyDown}
|
|
textarea, _ = textarea.Update(downMsg)
|
|
textarea, _ = textarea.Update(downMsg)
|
|
|
|
// We should be at the end of the last line.
|
|
if textarea.col != 20 || textarea.row != 2 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the 20th character of the last line")
|
|
}
|
|
|
|
// Now, for correct behavior, if we move right or left, we should forget
|
|
// (reset) the saved horizontal position. Since we assume the user wants to
|
|
// keep the cursor where it is horizontally. This is how most text areas
|
|
// work.
|
|
|
|
textarea, _ = textarea.Update(upMsg)
|
|
leftMsg := tea.KeyPressMsg{Code: tea.KeyLeft}
|
|
textarea, _ = textarea.Update(leftMsg)
|
|
|
|
if textarea.col != 4 || textarea.row != 1 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the 5th character of the second line")
|
|
}
|
|
|
|
// Going down now should keep us at the 4th column since we moved left and
|
|
// reset the horizontal position saved state.
|
|
textarea, _ = textarea.Update(downMsg)
|
|
if textarea.col != 4 || textarea.row != 2 {
|
|
t.Log(textarea.col)
|
|
t.Fatal("Expected cursor to be on the 4th character of the last line")
|
|
}
|
|
}
|
|
|
|
func TestView(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type want struct {
|
|
view string
|
|
cursorRow int
|
|
cursorCol int
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
modelFunc func(*Model) *Model
|
|
want want
|
|
}{
|
|
{
|
|
name: "placeholder",
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 Hello, World!
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "single line",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line")
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 the first line
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 14,
|
|
},
|
|
},
|
|
{
|
|
name: "multiple lines",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line\nthe second line\nthe third line")
|
|
|
|
return m
|
|
},
|
|
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",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line")
|
|
m.ShowLineNumbers = false
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> the first line
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 14,
|
|
},
|
|
},
|
|
{
|
|
name: "multipline lines without line numbers",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line\nthe second line\nthe third line")
|
|
m.ShowLineNumbers = false
|
|
|
|
return m
|
|
},
|
|
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",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line")
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 the first line
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 14,
|
|
},
|
|
},
|
|
{
|
|
name: "multiple lines and custom end of buffer character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line\nthe second line\nthe third line")
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
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",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line")
|
|
m.ShowLineNumbers = false
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> the first line
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 14,
|
|
},
|
|
},
|
|
{
|
|
name: "multiple lines without line numbers and custom end of buffer character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line\nthe second line\nthe third line")
|
|
m.ShowLineNumbers = false
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> the first line
|
|
> the second line
|
|
> the third line
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
cursorRow: 2,
|
|
cursorCol: 14,
|
|
},
|
|
},
|
|
{
|
|
name: "single line and custom prompt",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line")
|
|
m.Prompt = "* "
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
* 1 the first line
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 14,
|
|
},
|
|
},
|
|
{
|
|
name: "multiple lines and custom prompt",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetValue("the first line\nthe second line\nthe third line")
|
|
m.Prompt = "* "
|
|
|
|
return m
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
{
|
|
name: "set width",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(10)
|
|
|
|
input := "12"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 12
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 2,
|
|
},
|
|
},
|
|
{
|
|
name: "set width max length text minus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(10)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 123
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 3,
|
|
},
|
|
},
|
|
{
|
|
name: "set width max length text",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(10)
|
|
|
|
input := "1234"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 1234
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width max length text plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(10)
|
|
|
|
input := "12345"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 1234
|
|
> 5
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "set width set max width minus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.MaxWidth = 10
|
|
m.SetWidth(11)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 123
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 3,
|
|
},
|
|
},
|
|
{
|
|
name: "set width set max width",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.MaxWidth = 10
|
|
m.SetWidth(11)
|
|
|
|
input := "1234"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 1234
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width set max width plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.MaxWidth = 10
|
|
m.SetWidth(11)
|
|
|
|
input := "12345"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 1234
|
|
> 5
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "set width min width minus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(6)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 1
|
|
> 2
|
|
> 3
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 3,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width min width",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(7)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 1
|
|
> 2
|
|
> 3
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 3,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width min width no line numbers",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(0)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1
|
|
> 2
|
|
> 3
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 3,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width min width no line numbers no prompt",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = false
|
|
m.Prompt = ""
|
|
m.SetWidth(0)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
1
|
|
2
|
|
3
|
|
|
|
|
|
|
|
`),
|
|
cursorRow: 3,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width min width plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(8)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 12
|
|
> 3
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers max length text minus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(6)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 123
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 3,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers max length text",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(6)
|
|
|
|
input := "1234"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1234
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers max length text plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(6)
|
|
|
|
input := "12345"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1234
|
|
> 5
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "set width with style",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.SetWidth(12)
|
|
|
|
input := "1"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 1 1 │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "set width with style max width minus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.SetWidth(12)
|
|
|
|
input := "123"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 1 123 │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 3,
|
|
},
|
|
},
|
|
{
|
|
name: "set width with style max width",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.SetWidth(12)
|
|
|
|
input := "1234"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 1 1234│
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width with style max width plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.SetWidth(12)
|
|
|
|
input := "12345"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 1 1234│
|
|
│> 5 │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers with style",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(12)
|
|
|
|
input := "123456"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 123456 │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 6,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers with style max width minus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(12)
|
|
|
|
input := "1234567"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 1234567 │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 0,
|
|
cursorCol: 7,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers with style max width",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(12)
|
|
|
|
input := "12345678"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 12345678│
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "set width without line numbers with style max width plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
s := m.Styles()
|
|
s.Focused.Base = lipgloss.NewStyle().Border(lipgloss.NormalBorder())
|
|
m.SetStyles(s)
|
|
m.Focus()
|
|
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(12)
|
|
|
|
input := "123456789"
|
|
m = sendString(m, input)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
┌──────────┐
|
|
│> 12345678│
|
|
│> 9 │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
│> │
|
|
└──────────┘
|
|
`),
|
|
cursorRow: 1,
|
|
cursorCol: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder min width",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetWidth(0)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 H
|
|
> e
|
|
> l
|
|
> l
|
|
> o
|
|
> ,
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line"
|
|
m.ShowLineNumbers = false
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> placeholder the first line
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line"
|
|
m.ShowLineNumbers = false
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> placeholder the first line
|
|
> placeholder the second line
|
|
> placeholder the third line
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line with line numbers",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line"
|
|
m.ShowLineNumbers = true
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 placeholder the first line
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines with line numbers",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line"
|
|
m.ShowLineNumbers = true
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 placeholder the first line
|
|
> placeholder the second line
|
|
> placeholder the third line
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line with end of buffer character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line"
|
|
m.ShowLineNumbers = false
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> placeholder the first line
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines with with end of buffer character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line"
|
|
m.ShowLineNumbers = false
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> placeholder the first line
|
|
> placeholder the second line
|
|
> placeholder the third line
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line with line numbers and end of buffer character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line"
|
|
m.ShowLineNumbers = true
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 placeholder the first line
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines with line numbers and end of buffer character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line"
|
|
m.ShowLineNumbers = true
|
|
m.EndOfBufferCharacter = '*'
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 placeholder the first line
|
|
> placeholder the second line
|
|
> placeholder the third line
|
|
> *
|
|
> *
|
|
> *
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line that is longer than max width",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line that is longer than the max width"
|
|
m.SetWidth(40)
|
|
m.ShowLineNumbers = false
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> placeholder the first line that is
|
|
> longer than the max width
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines that are longer than max width",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line that is longer than the max width\nplaceholder the second line that is longer than the max width"
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(40)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> placeholder the first line that is
|
|
> longer than the max width
|
|
> placeholder the second line that is
|
|
> longer than the max width
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line that is longer than max width with line numbers",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line that is longer than the max width"
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(40)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 placeholder the first line that is
|
|
> longer than the max width
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines that are longer than max width with line numbers",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "placeholder the first line that is longer than the max width\nplaceholder the second line that is longer than the max width"
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(40)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 placeholder the first line that is
|
|
> longer than the max width
|
|
> placeholder the second line that
|
|
> is longer than the max width
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line that is longer than max width at limit",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "123456789012345678"
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 123456789012345678
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line that is longer than max width at limit plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "1234567890123456789"
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 123456789012345678
|
|
> 9
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line that is longer than max width with line numbers at limit",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "12345678901234"
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 12345678901234
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder single line that is longer than max width with line numbers at limit plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "123456789012345"
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 12345678901234
|
|
> 5
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines that are longer than max width at limit",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "123456789012345678\n123456789012345678"
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 123456789012345678
|
|
> 123456789012345678
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines that are longer than max width at limit plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "1234567890123456789\n1234567890123456789"
|
|
m.ShowLineNumbers = false
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 123456789012345678
|
|
> 9
|
|
> 123456789012345678
|
|
> 9
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines that are longer than max width with line numbers at limit",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "12345678901234\n12345678901234"
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 12345678901234
|
|
> 12345678901234
|
|
>
|
|
>
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder multiple lines that are longer than max width with line numbers at limit plus one",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "123456789012345\n123456789012345"
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(20)
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 12345678901234
|
|
> 5
|
|
> 12345678901234
|
|
> 5
|
|
>
|
|
>
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "placeholder chinese character",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.Placeholder = "输入消息..."
|
|
m.ShowLineNumbers = true
|
|
m.SetWidth(20)
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 输入消息...
|
|
>
|
|
>
|
|
>
|
|
>
|
|
>
|
|
|
|
`),
|
|
},
|
|
},
|
|
{
|
|
name: "page up moves to beginning when near top",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = true
|
|
m.SetHeight(4)
|
|
m.SetWidth(20)
|
|
|
|
lines := make([]string, 10)
|
|
for i := range 10 {
|
|
lines[i] = fmt.Sprintf("Line %d", i+1)
|
|
}
|
|
m.SetValue(strings.Join(lines, "\n"))
|
|
m.viewport.SetContent(m.view()) // force setting of viewport content.
|
|
|
|
m.row = 3
|
|
m.col = 0
|
|
m.viewport.SetYOffset(0)
|
|
m.PageUp()
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 1 Line 1
|
|
> 2 Line 2
|
|
> 3 Line 3
|
|
> 4 Line 4
|
|
`),
|
|
cursorRow: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "page up snaps to first visible line when not on it",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = true
|
|
m.SetHeight(4)
|
|
m.SetWidth(20)
|
|
|
|
lines := make([]string, 10)
|
|
for i := range 10 {
|
|
lines[i] = fmt.Sprintf("Line %d", i+1)
|
|
}
|
|
m.SetValue(strings.Join(lines, "\n"))
|
|
m.viewport.SetContent(m.view()) // force setting of viewport content.
|
|
|
|
m.row = 5
|
|
m.col = 0
|
|
m.viewport.SetYOffset(3)
|
|
m.PageUp()
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 4 Line 4
|
|
> 5 Line 5
|
|
> 6 Line 6
|
|
> 7 Line 7
|
|
`),
|
|
cursorRow: 3,
|
|
},
|
|
},
|
|
{
|
|
name: "page up moves up by full page when on first visible line",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.ShowLineNumbers = true
|
|
m.SetHeight(3)
|
|
m.SetWidth(20)
|
|
|
|
lines := make([]string, 10)
|
|
for i := range 10 {
|
|
lines[i] = fmt.Sprintf("Line %d", i+1)
|
|
}
|
|
m.SetValue(strings.Join(lines, "\n"))
|
|
m.viewport.SetContent(m.view()) // force setting of viewport content.
|
|
|
|
m.row = 5
|
|
m.col = 0
|
|
m.viewport.SetYOffset(5)
|
|
m.PageUp()
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 3 Line 3
|
|
> 4 Line 4
|
|
> 5 Line 5
|
|
`),
|
|
cursorRow: 2,
|
|
},
|
|
},
|
|
{
|
|
name: "page down moves to end when near bottom",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetHeight(3)
|
|
m.SetWidth(20)
|
|
|
|
lines := make([]string, 10)
|
|
for i := range 10 {
|
|
lines[i] = fmt.Sprintf("Line %d", i+1)
|
|
}
|
|
m.SetValue(strings.Join(lines, "\n"))
|
|
m.viewport.SetContent(m.view()) // force setting of viewport content.
|
|
|
|
m.row = 8
|
|
m.col = 0
|
|
m.viewport.SetYOffset(7)
|
|
m.PageDown()
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 8 Line 8
|
|
> 9 Line 9
|
|
> 10 Line 10
|
|
`),
|
|
cursorRow: 9,
|
|
},
|
|
},
|
|
{
|
|
name: "page down snaps to last visible line when not on it",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetHeight(3)
|
|
m.SetWidth(20)
|
|
|
|
lines := make([]string, 10)
|
|
for i := range 10 {
|
|
lines[i] = fmt.Sprintf("Line %d", i+1)
|
|
}
|
|
m.SetValue(strings.Join(lines, "\n"))
|
|
m.viewport.SetContent(m.view()) // force setting of viewport content.
|
|
|
|
m.row = 3
|
|
m.col = 0
|
|
m.viewport.SetYOffset(3)
|
|
m.PageDown()
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 4 Line 4
|
|
> 5 Line 5
|
|
> 6 Line 6
|
|
`),
|
|
cursorRow: 5,
|
|
},
|
|
},
|
|
{
|
|
name: "page down moves down by full page when on last visible line",
|
|
modelFunc: func(m *Model) *Model {
|
|
m.SetHeight(3)
|
|
m.SetWidth(20)
|
|
|
|
lines := make([]string, 10)
|
|
for i := range 10 {
|
|
lines[i] = fmt.Sprintf("Line %d", i+1)
|
|
}
|
|
m.SetValue(strings.Join(lines, "\n"))
|
|
m.viewport.SetContent(m.view()) // force setting of viewport content.
|
|
|
|
m.row = 4
|
|
m.col = 0
|
|
m.viewport.SetYOffset(2)
|
|
m.PageDown()
|
|
|
|
return m
|
|
},
|
|
want: want{
|
|
view: heredoc.Doc(`
|
|
> 6 Line 6
|
|
> 7 Line 7
|
|
> 8 Line 8
|
|
`),
|
|
cursorRow: 7,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
textarea := newTextArea()
|
|
|
|
if tt.modelFunc != nil {
|
|
textarea = tt.modelFunc(textarea)
|
|
}
|
|
|
|
view := stripString(textarea.View())
|
|
wantView := stripString(tt.want.view)
|
|
|
|
if view != wantView {
|
|
t.Fatalf("Want:\n%v\nGot:\n%v\n", wantView, 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWord(t *testing.T) {
|
|
textarea := newTextArea()
|
|
|
|
textarea.SetHeight(3)
|
|
textarea.SetWidth(20)
|
|
textarea.CharLimit = 500
|
|
|
|
textarea, _ = textarea.Update(nil)
|
|
|
|
t.Run("regular input", func(t *testing.T) {
|
|
input := "Word1 Word2 Word3 Word4"
|
|
for _, k := range input {
|
|
textarea, _ = textarea.Update(keyPress(k))
|
|
textarea.View()
|
|
}
|
|
|
|
expect := "Word4"
|
|
if word := textarea.Word(); word != expect {
|
|
t.Fatalf("Expected last word to be '%s', got '%s'", expect, word)
|
|
}
|
|
})
|
|
|
|
t.Run("navigate", func(t *testing.T) {
|
|
for _, k := range []tea.KeyPressMsg{
|
|
{Code: tea.KeyLeft, Mod: tea.ModAlt, Text: "alt+left"},
|
|
{Code: tea.KeyLeft, Mod: tea.ModAlt, Text: "alt+left"},
|
|
{Code: tea.KeyRight, Text: "right"},
|
|
} {
|
|
textarea, _ = textarea.Update(k)
|
|
textarea.View()
|
|
}
|
|
|
|
expect := "Word3"
|
|
if word := textarea.Word(); word != expect {
|
|
t.Fatalf("Expected last word to be '%s', got '%s'", expect, word)
|
|
}
|
|
})
|
|
|
|
t.Run("delete", func(t *testing.T) {
|
|
for _, k := range []tea.KeyPressMsg{
|
|
{Code: tea.KeyEnd, Text: "end"},
|
|
{Code: tea.KeyBackspace, Mod: tea.ModAlt, Text: "alt+backspace"},
|
|
{Code: tea.KeyBackspace, Mod: tea.ModAlt, Text: "alt+backspace"},
|
|
{Code: tea.KeyBackspace, Text: "backspace"},
|
|
} {
|
|
textarea, _ = textarea.Update(k)
|
|
textarea.View()
|
|
}
|
|
|
|
expect := "Word2"
|
|
if word := textarea.Word(); word != expect {
|
|
t.Fatalf("Expected last word to be '%s', got '%s'", expect, word)
|
|
}
|
|
})
|
|
}
|
|
|
|
func newTextArea() *Model {
|
|
textarea := New()
|
|
|
|
textarea.Prompt = "> "
|
|
textarea.Placeholder = "Hello, World!"
|
|
|
|
textarea.Focus()
|
|
|
|
textarea, _ = textarea.Update(nil)
|
|
|
|
return textarea
|
|
}
|
|
|
|
func keyPress(key rune) tea.Msg {
|
|
return tea.KeyPressMsg{Code: key, Text: string(key)}
|
|
}
|
|
|
|
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 := ansi.Strip(str)
|
|
ss := strings.Split(s, "\n")
|
|
|
|
var lines []string
|
|
for _, l := range ss {
|
|
trim := strings.TrimRightFunc(l, unicode.IsSpace)
|
|
if trim != "" {
|
|
lines = append(lines, trim)
|
|
}
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|