diff --git a/internal/tui/exp/list/list.go b/internal/tui/exp/list/list.go index d2cb49d7ab09048e518dbff8ce55427d4a16dc75..e9e304fac8463cfce42dc02e71962879abaa5643 100644 --- a/internal/tui/exp/list/list.go +++ b/internal/tui/exp/list/list.go @@ -1,41 +1,60 @@ package list import ( + "strings" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/crush/internal/tui/components/core/layout" "github.com/charmbracelet/crush/internal/tui/util" + "github.com/charmbracelet/lipgloss/v2" ) type Item interface { util.Model layout.Sizeable + ID() string } type List interface { util.Model + layout.Sizeable + layout.Focusable + SetItems(items []Item) tea.Cmd +} + +type direction int + +const ( + Forward direction = iota + Backward +) + +const ( + NotFound = -1 +) + +type renderedItem struct { + id string + view string + height int } type list struct { width, height int + offset int gap int + direction direction + selectedItem string + focused bool - items []Item - - // Filter options - filterable bool - filterPlaceholder string + items []Item + renderedItems []renderedItem + rendered string + isReady bool } type listOption func(*list) -// WithFilterable enables filtering on the list. -func WithFilterable(placeholder string) listOption { - return func(l *list) { - l.filterable = true - l.filterPlaceholder = placeholder - } -} - // WithItems sets the initial items for the list. func WithItems(items ...Item) listOption { return func(l *list) { @@ -58,9 +77,24 @@ func WithGap(gap int) listOption { } } +// WithDirection sets the direction of the list. +func WithDirection(dir direction) listOption { + return func(l *list) { + l.direction = dir + } +} + +// WithSelectedItem sets the initially selected item in the list. +func WithSelectedItem(id string) listOption { + return func(l *list) { + l.selectedItem = id + } +} + func New(opts ...listOption) List { list := &list{ - items: make([]Item, 0), + items: make([]Item, 0), + direction: Forward, } for _, opt := range opts { opt(list) @@ -73,15 +107,422 @@ func (l *list) Init() tea.Cmd { if l.height <= 0 || l.width <= 0 { return nil } - return nil + if len(l.items) == 0 { + return nil + } + var cmds []tea.Cmd + for _, item := range l.items { + cmd := item.Init() + cmds = append(cmds, cmd) + } + cmds = append(cmds, l.renderItems()) + return tea.Batch(cmds...) } // Update implements List. func (l *list) Update(tea.Msg) (tea.Model, tea.Cmd) { - panic("unimplemented") + return l, nil } // View implements List. func (l *list) View() string { - panic("unimplemented") + if l.height <= 0 || l.width <= 0 { + return "" + } + view := l.rendered + lines := strings.Split(view, "\n") + + start, end := l.viewPosition(len(lines)) + lines = lines[start:end] + return strings.Join(lines, "\n") +} + +func (l *list) viewPosition(total int) (int, int) { + start, end := 0, 0 + if l.direction == Forward { + start = max(0, l.offset) + end = min(l.offset+l.listHeight(), total) + } else { + start = max(0, total-l.offset-l.listHeight()) + end = max(0, total-l.offset) + } + return start, end +} + +func (l *list) renderItem(item Item) renderedItem { + view := item.View() + return renderedItem{ + id: item.ID(), + view: view, + height: lipgloss.Height(view), + } +} + +func (l *list) renderView() { + var sb strings.Builder + for i, rendered := range l.renderedItems { + sb.WriteString(rendered.view) + if i < len(l.renderedItems)-1 { + sb.WriteString(strings.Repeat("\n", l.gap+1)) + } + } + l.rendered = sb.String() +} + +func (l *list) incrementOffset(n int) { + if !l.isReady { + return + } + renderedHeight := lipgloss.Height(l.rendered) + // no need for offset + if renderedHeight <= l.listHeight() { + return + } + maxOffset := renderedHeight - l.listHeight() + n = min(n, maxOffset-l.offset) + if n <= 0 { + return + } + l.offset += n +} + +func (l *list) decrementOffset(n int) { + if !l.isReady { + return + } + n = min(n, l.offset) + if n <= 0 { + return + } + l.offset -= n + if l.offset < 0 { + l.offset = 0 + } +} + +func (l *list) MoveUp(n int) { + if l.direction == Forward { + l.decrementOffset(n) + } else { + l.incrementOffset(n) + } +} + +func (l *list) MoveDown(n int) { + if l.direction == Forward { + l.incrementOffset(n) + } else { + l.decrementOffset(n) + } +} + +func (l *list) firstSelectableItemBefore(inx int) int { + for i := inx - 1; i >= 0; i-- { + if _, ok := l.items[i].(layout.Focusable); ok { + return i + } + } + return NotFound +} + +func (l *list) firstSelectableItemAfter(inx int) int { + for i := inx + 1; i < len(l.items); i++ { + if _, ok := l.items[i].(layout.Focusable); ok { + return i + } + } + return NotFound +} + +func (l *list) moveToSelected() { + if l.selectedItem == "" || !l.isReady { + return + } + currentPosition := 0 + start, end := l.viewPosition(lipgloss.Height(l.rendered)) + for _, item := range l.renderedItems { + if item.id == l.selectedItem { + if start <= currentPosition && currentPosition <= end { + return + } + // we need to go up + if currentPosition < start { + l.MoveUp(start - currentPosition) + } + // we need to go down + if currentPosition > end { + l.MoveDown(currentPosition - end) + } + } + currentPosition += item.height + l.gap + } +} + +func (l *list) SelectItemAbove() tea.Cmd { + if !l.isReady { + return nil + } + var cmds []tea.Cmd + for i, item := range l.items { + if l.selectedItem == item.ID() { + inx := l.firstSelectableItemBefore(i) + if inx == NotFound { + // no item above + return nil + } + // blur the current item + if focusable, ok := item.(layout.Focusable); ok { + cmds = append(cmds, focusable.Blur()) + } + // rerender the item + l.renderedItems[i] = l.renderItem(item) + // focus the item above + above := l.items[inx] + if focusable, ok := above.(layout.Focusable); ok { + cmds = append(cmds, focusable.Focus()) + } + // rerender the item + l.renderedItems[inx] = l.renderItem(above) + l.selectedItem = above.ID() + break + } + } + l.renderView() + l.moveToSelected() + return tea.Batch(cmds...) +} + +func (l *list) SelectItemBelow() tea.Cmd { + if !l.isReady { + return nil + } + var cmds []tea.Cmd + for i, item := range l.items { + if l.selectedItem == item.ID() { + inx := l.firstSelectableItemAfter(i) + if inx == NotFound { + // no item below + return nil + } + // blur the current item + if focusable, ok := item.(layout.Focusable); ok { + cmds = append(cmds, focusable.Blur()) + } + // rerender the item + l.renderedItems[i] = l.renderItem(item) + + // focus the item below + below := l.items[inx] + if focusable, ok := below.(layout.Focusable); ok { + cmds = append(cmds, focusable.Focus()) + } + // rerender the item + l.renderedItems[inx] = l.renderItem(below) + l.selectedItem = below.ID() + break + } + } + + l.renderView() + l.moveToSelected() + return tea.Batch(cmds...) +} + +func (l *list) GoToTop() tea.Cmd { + if !l.isReady { + return nil + } + l.offset = 0 + l.direction = Forward + return tea.Batch(l.selectFirstItem(), l.renderForward()) +} + +func (l *list) GoToBottom() tea.Cmd { + if !l.isReady { + return nil + } + l.offset = 0 + l.direction = Backward + + return tea.Batch(l.selectLastItem(), l.renderBackward()) +} + +func (l *list) renderForward() tea.Cmd { + // TODO: figure out a way to preserve items that did not change + l.renderedItems = make([]renderedItem, 0) + currentHeight := 0 + currentIndex := 0 + for i, item := range l.items { + currentIndex = i + if currentHeight > l.listHeight() { + break + } + rendered := l.renderItem(item) + l.renderedItems = append(l.renderedItems, rendered) + currentHeight += rendered.height + l.gap + } + + // initial render + l.renderView() + + if currentIndex == len(l.items)-1 { + l.isReady = true + return nil + } + // render the rest + return func() tea.Msg { + for i := currentIndex; i < len(l.items); i++ { + rendered := l.renderItem(l.items[i]) + l.renderedItems = append(l.renderedItems, rendered) + } + l.renderView() + l.isReady = true + return nil + } +} + +func (l *list) renderBackward() tea.Cmd { + // TODO: figure out a way to preserve items that did not change + l.renderedItems = make([]renderedItem, 0) + currentHeight := 0 + currentIndex := 0 + for i := len(l.items) - 1; i >= 0; i-- { + currentIndex = i + if currentHeight > l.listHeight() { + break + } + rendered := l.renderItem(l.items[i]) + l.renderedItems = append([]renderedItem{rendered}, l.renderedItems...) + currentHeight += rendered.height + l.gap + } + // initial render + l.renderView() + if currentIndex == len(l.items)-1 { + l.isReady = true + return nil + } + return func() tea.Msg { + for i := currentIndex; i >= 0; i-- { + rendered := l.renderItem(l.items[i]) + l.renderedItems = append([]renderedItem{rendered}, l.renderedItems...) + } + l.renderView() + l.isReady = true + return nil + } +} + +func (l *list) selectFirstItem() tea.Cmd { + var cmd tea.Cmd + inx := l.firstSelectableItemAfter(-1) + if inx != NotFound { + l.selectedItem = l.items[inx].ID() + if focusable, ok := l.items[inx].(layout.Focusable); ok { + cmd = focusable.Focus() + } + } + return cmd +} + +func (l *list) selectLastItem() tea.Cmd { + var cmd tea.Cmd + inx := l.firstSelectableItemBefore(len(l.items)) + if inx != NotFound { + l.selectedItem = l.items[inx].ID() + if focusable, ok := l.items[inx].(layout.Focusable); ok { + cmd = focusable.Focus() + } + } + return cmd +} + +func (l *list) renderItems() tea.Cmd { + if l.height <= 0 || l.width <= 0 { + return nil + } + if len(l.items) == 0 { + return nil + } + + if l.selectedItem == "" { + if l.direction == Forward { + l.selectFirstItem() + } else { + l.selectLastItem() + } + } + return l.renderBackward() +} + +func (l *list) listHeight() int { + // for the moment its the same + return l.height +} + +func (l *list) SetItems(items []Item) tea.Cmd { + l.items = items + var cmds []tea.Cmd + for _, item := range l.items { + cmds = append(cmds, item.Init()) + // Set height to 0 to let the item calculate its own height + cmds = append(cmds, item.SetSize(l.width, 0)) + } + cmds = append(cmds, l.renderItems()) + return tea.Batch(cmds...) +} + +// GetSize implements List. +func (l *list) GetSize() (int, int) { + return l.width, l.height +} + +// SetSize implements List. +func (l *list) SetSize(width int, height int) tea.Cmd { + l.width = width + l.height = height + var cmds []tea.Cmd + for _, item := range l.items { + cmds = append(cmds, item.SetSize(width, height)) + } + cmds = append(cmds, l.renderItems()) + return tea.Batch(cmds...) +} + +// Blur implements List. +func (l *list) Blur() tea.Cmd { + var cmd tea.Cmd + l.focused = false + for i, item := range l.items { + if item.ID() != l.selectedItem { + continue + } + if focusable, ok := item.(layout.Focusable); ok { + cmd = focusable.Blur() + } + l.renderedItems[i] = l.renderItem(item) + } + l.renderView() + return cmd +} + +// Focus implements List. +func (l *list) Focus() tea.Cmd { + var cmd tea.Cmd + l.focused = true + for i, item := range l.items { + if item.ID() != l.selectedItem { + continue + } + if focusable, ok := item.(layout.Focusable); ok { + cmd = focusable.Focus() + } + l.renderedItems[i] = l.renderItem(item) + } + l.renderView() + return cmd +} + +// IsFocused implements List. +func (l *list) IsFocused() bool { + return l.focused } diff --git a/internal/tui/exp/list/list_test.go b/internal/tui/exp/list/list_test.go new file mode 100644 index 0000000000000000000000000000000000000000..54f6f3fcac9878e29f2e309e1aee902cefdf46c0 --- /dev/null +++ b/internal/tui/exp/list/list_test.go @@ -0,0 +1,462 @@ +package list + +import ( + "fmt" + "testing" + + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/crush/internal/tui/components/core/layout" + "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/x/exp/golden" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestBackwardList(t *testing.T) { + t.Run("within height", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward), WithGap(1)).(*list) + l.SetSize(10, 20) + items := []Item{} + for i := range 5 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + // should select the last item + assert.Equal(t, l.selectedItem, items[len(items)-1].ID()) + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should not change selected item", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 5 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(WithDirection(Backward), WithGap(1), WithSelectedItem(items[2].ID())).(*list) + l.SetSize(10, 20) + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + // should select the last item + assert.Equal(t, l.selectedItem, items[2].ID()) + }) + t.Run("more than height", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("more than height multi line", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d\nLine2", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move up", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.MoveUp(1) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move at max to the top", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.MoveUp(100) + assert.Equal(t, l.offset, lipgloss.Height(l.rendered)-l.listHeight()) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should do nothing with wrong move number", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.MoveUp(-10) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move to the top", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.GoToTop() + assert.Equal(t, l.direction, Forward) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should select the item above", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + selectedInx := len(l.items) - 2 + currentItem := items[len(l.items)-1] + nextItem := items[selectedInx] + assert.False(t, nextItem.(SimpleItem).IsFocused()) + assert.True(t, currentItem.(SimpleItem).IsFocused()) + cmd = l.SelectItemAbove() + if cmd != nil { + cmd() + } + + assert.Equal(t, l.selectedItem, l.items[selectedInx].ID()) + assert.True(t, l.items[selectedInx].(SimpleItem).IsFocused()) + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move the view to be able to see the selected item", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + for range 5 { + cmd = l.SelectItemAbove() + if cmd != nil { + cmd() + } + } + golden.RequireEqual(t, []byte(l.View())) + }) +} + +func TestForwardList(t *testing.T) { + t.Run("within height", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward), WithGap(1)).(*list) + l.SetSize(10, 20) + items := []Item{} + for i := range 5 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + // should select the last item + assert.Equal(t, l.selectedItem, items[0].ID()) + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should not change selected item", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 5 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(WithDirection(Forward), WithGap(1), WithSelectedItem(items[2].ID())).(*list) + l.SetSize(10, 20) + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + // should select the last item + assert.Equal(t, l.selectedItem, items[2].ID()) + }) + t.Run("more than height", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("more than height multi line", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d\nLine2", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move down", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.MoveDown(1) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move at max to the top", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.MoveDown(100) + assert.Equal(t, l.offset, lipgloss.Height(l.rendered)-l.listHeight()) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should do nothing with wrong move number", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.MoveDown(-10) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move to the bottom", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + l.GoToBottom() + assert.Equal(t, l.direction, Backward) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should select the item below", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Forward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + selectedInx := 1 + currentItem := items[0] + nextItem := items[selectedInx] + assert.False(t, nextItem.(SimpleItem).IsFocused()) + assert.True(t, currentItem.(SimpleItem).IsFocused()) + cmd = l.SelectItemBelow() + if cmd != nil { + cmd() + } + + assert.Equal(t, l.selectedItem, l.items[selectedInx].ID()) + assert.True(t, l.items[selectedInx].(SimpleItem).IsFocused()) + + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should move the view to be able to see the selected item", func(t *testing.T) { + t.Parallel() + l := New(WithDirection(Backward)).(*list) + l.SetSize(10, 5) + items := []Item{} + for i := range 10 { + item := NewSimpleItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + cmd := l.SetItems(items) + if cmd != nil { + cmd() + } + + for range 5 { + cmd = l.SelectItemBelow() + if cmd != nil { + cmd() + } + } + golden.RequireEqual(t, []byte(l.View())) + }) +} + +type SimpleItem interface { + Item + layout.Focusable +} + +type simpleItem struct { + width int + content string + id string + focused bool +} + +func NewSimpleItem(content string) SimpleItem { + return &simpleItem{ + width: 0, + content: content, + focused: false, + id: uuid.NewString(), + } +} + +func (s *simpleItem) ID() string { + return s.id +} + +func (s *simpleItem) Init() tea.Cmd { + return nil +} + +func (s *simpleItem) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + return s, nil +} + +func (s *simpleItem) View() string { + if s.focused { + return lipgloss.NewStyle().BorderLeft(true).BorderStyle(lipgloss.NormalBorder()).Width(s.width).Render(s.content) + } + return lipgloss.NewStyle().Width(s.width).Render(s.content) +} + +func (l *simpleItem) GetSize() (int, int) { + return l.width, 0 +} + +// SetSize implements Item. +func (s *simpleItem) SetSize(width int, height int) tea.Cmd { + s.width = width + return nil +} + +// Blur implements SimpleItem. +func (s *simpleItem) Blur() tea.Cmd { + s.focused = false + return nil +} + +// Focus implements SimpleItem. +func (s *simpleItem) Focus() tea.Cmd { + s.focused = true + return nil +} + +// IsFocused implements SimpleItem. +func (s *simpleItem) IsFocused() bool { + return s.focused +} diff --git a/internal/tui/exp/list/testdata/TestBackwardList/more_than_height.golden b/internal/tui/exp/list/testdata/TestBackwardList/more_than_height.golden new file mode 100644 index 0000000000000000000000000000000000000000..b8fd0efdb00bce286317007e40b2af335d22942f --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/more_than_height.golden @@ -0,0 +1,5 @@ +Item 5 +Item 6 +Item 7 +Item 8 +│Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/more_than_height_multi_line.golden b/internal/tui/exp/list/testdata/TestBackwardList/more_than_height_multi_line.golden new file mode 100644 index 0000000000000000000000000000000000000000..785a18b24f21ee9f6a58bf0a0b540bff4b5c097d --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/more_than_height_multi_line.golden @@ -0,0 +1,5 @@ +Line2 +Item 8 +Line2 +│Item 9 +│Line2 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/should_do_nothing_with_wrong_move_number.golden b/internal/tui/exp/list/testdata/TestBackwardList/should_do_nothing_with_wrong_move_number.golden new file mode 100644 index 0000000000000000000000000000000000000000..b8fd0efdb00bce286317007e40b2af335d22942f --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/should_do_nothing_with_wrong_move_number.golden @@ -0,0 +1,5 @@ +Item 5 +Item 6 +Item 7 +Item 8 +│Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/should_move_at_max_to_the_top.golden b/internal/tui/exp/list/testdata/TestBackwardList/should_move_at_max_to_the_top.golden new file mode 100644 index 0000000000000000000000000000000000000000..b811a8818ab2e87fdd09b64f69dcca8694a5b77c --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/should_move_at_max_to_the_top.golden @@ -0,0 +1,5 @@ +Item 0 +Item 1 +Item 2 +Item 3 +Item 4 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/should_move_the_view_to_be_able_to_see_the_selected_item.golden b/internal/tui/exp/list/testdata/TestBackwardList/should_move_the_view_to_be_able_to_see_the_selected_item.golden new file mode 100644 index 0000000000000000000000000000000000000000..5fb57a04f2136e6f00fa780620e61301a4d5fe9c --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/should_move_the_view_to_be_able_to_see_the_selected_item.golden @@ -0,0 +1,5 @@ +│Item 4 +Item 5 +Item 6 +Item 7 +Item 8 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/should_move_to_the_top.golden b/internal/tui/exp/list/testdata/TestBackwardList/should_move_to_the_top.golden new file mode 100644 index 0000000000000000000000000000000000000000..7ca9b9f9cec94e77c1978e1265a1ee4f10da2e4b --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/should_move_to_the_top.golden @@ -0,0 +1,5 @@ +│Item 0 +Item 1 +Item 2 +Item 3 +Item 4 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/should_move_up.golden b/internal/tui/exp/list/testdata/TestBackwardList/should_move_up.golden new file mode 100644 index 0000000000000000000000000000000000000000..97a5e679968d701bfd0c4df1ab01e4d64891bbfe --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/should_move_up.golden @@ -0,0 +1,5 @@ +Item 4 +Item 5 +Item 6 +Item 7 +Item 8 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/should_select_the_item_above.golden b/internal/tui/exp/list/testdata/TestBackwardList/should_select_the_item_above.golden new file mode 100644 index 0000000000000000000000000000000000000000..d2e6c78521781d77668673db6447d67dacd4097a --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/should_select_the_item_above.golden @@ -0,0 +1,5 @@ +Item 5 +Item 6 +Item 7 +│Item 8 +Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestBackwardList/within_height.golden b/internal/tui/exp/list/testdata/TestBackwardList/within_height.golden new file mode 100644 index 0000000000000000000000000000000000000000..a4b10135f97530577ee977b4325e30fe34184f21 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestBackwardList/within_height.golden @@ -0,0 +1,11 @@ +Item 0 + +Item 0 + +Item 1 + +Item 2 + +Item 3 + +│Item 4 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/more_than_height.golden b/internal/tui/exp/list/testdata/TestForwardList/more_than_height.golden new file mode 100644 index 0000000000000000000000000000000000000000..7ca9b9f9cec94e77c1978e1265a1ee4f10da2e4b --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/more_than_height.golden @@ -0,0 +1,5 @@ +│Item 0 +Item 1 +Item 2 +Item 3 +Item 4 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/more_than_height_multi_line.golden b/internal/tui/exp/list/testdata/TestForwardList/more_than_height_multi_line.golden new file mode 100644 index 0000000000000000000000000000000000000000..f8a79a980eb0cd28f70f1593705e63c6736f2eaa --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/more_than_height_multi_line.golden @@ -0,0 +1,5 @@ +│Item 0 +│Line2 +Item 1 +Line2 +Item 2 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/should_do_nothing_with_wrong_move_number.golden b/internal/tui/exp/list/testdata/TestForwardList/should_do_nothing_with_wrong_move_number.golden new file mode 100644 index 0000000000000000000000000000000000000000..7ca9b9f9cec94e77c1978e1265a1ee4f10da2e4b --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/should_do_nothing_with_wrong_move_number.golden @@ -0,0 +1,5 @@ +│Item 0 +Item 1 +Item 2 +Item 3 +Item 4 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/should_move_at_max_to_the_top.golden b/internal/tui/exp/list/testdata/TestForwardList/should_move_at_max_to_the_top.golden new file mode 100644 index 0000000000000000000000000000000000000000..33aff077aeecc3b1f74ca93b7c7589b908abf16d --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/should_move_at_max_to_the_top.golden @@ -0,0 +1,5 @@ +Item 5 +Item 6 +Item 7 +Item 8 +Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/should_move_down.golden b/internal/tui/exp/list/testdata/TestForwardList/should_move_down.golden new file mode 100644 index 0000000000000000000000000000000000000000..f01acdd8274b7ebb8307ba004b05ab205868cf71 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/should_move_down.golden @@ -0,0 +1,5 @@ +Item 1 +Item 2 +Item 3 +Item 4 +Item 5 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/should_move_the_view_to_be_able_to_see_the_selected_item.golden b/internal/tui/exp/list/testdata/TestForwardList/should_move_the_view_to_be_able_to_see_the_selected_item.golden new file mode 100644 index 0000000000000000000000000000000000000000..b8fd0efdb00bce286317007e40b2af335d22942f --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/should_move_the_view_to_be_able_to_see_the_selected_item.golden @@ -0,0 +1,5 @@ +Item 5 +Item 6 +Item 7 +Item 8 +│Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/should_move_to_the_bottom.golden b/internal/tui/exp/list/testdata/TestForwardList/should_move_to_the_bottom.golden new file mode 100644 index 0000000000000000000000000000000000000000..b8fd0efdb00bce286317007e40b2af335d22942f --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/should_move_to_the_bottom.golden @@ -0,0 +1,5 @@ +Item 5 +Item 6 +Item 7 +Item 8 +│Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/should_select_the_item_below.golden b/internal/tui/exp/list/testdata/TestForwardList/should_select_the_item_below.golden new file mode 100644 index 0000000000000000000000000000000000000000..7368e97d2ca758d46ae230cd5267cd93c4ee4dec --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/should_select_the_item_below.golden @@ -0,0 +1,5 @@ +Item 0 +│Item 1 +Item 2 +Item 3 +Item 4 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestForwardList/within_height.golden b/internal/tui/exp/list/testdata/TestForwardList/within_height.golden new file mode 100644 index 0000000000000000000000000000000000000000..1450d9e9ac0bc14266454379b8736df4b7775bb0 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestForwardList/within_height.golden @@ -0,0 +1,11 @@ +│Item 0 + +│Item 0 + +Item 1 + +Item 2 + +Item 3 + +Item 4 \ No newline at end of file