@@ -315,6 +315,243 @@ func TestListMovement(t *testing.T) {
315 assert.Equal(t, 0, l.offset)
315 assert.Equal(t, 0, l.offset)
316 golden.RequireEqual(t, []byte(l.View()))
316 golden.RequireEqual(t, []byte(l.View()))
319+ t.Run("should not change offset when new items are appended and we are at the bottom in backwards list", func(t *testing.T) {
323+ content := strings.Repeat(fmt.Sprintf("Item %d\n", i), i+1)
324+ content = strings.TrimSuffix(content, "\n")
325+ item := NewSelectableItem(content)
326+ items = append(items, item)
328+ l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item])
329+ execCmd(l, l.Init())
330+ execCmd(l, l.AppendItem(NewSelectableItem("Testing")))
332+ assert.Equal(t, 0, l.offset)
333+ golden.RequireEqual(t, []byte(l.View()))
336+ t.Run("should stay at the position it is when new items are added but we moved up in backwards list", func(t *testing.T) {
340+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
341+ items = append(items, item)
343+ l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item])
344+ execCmd(l, l.Init())
346+ execCmd(l, l.MoveUp(2))
347+ viewBefore := l.View()
348+ execCmd(l, l.AppendItem(NewSelectableItem("Testing\nHello\n")))
349+ viewAfter := l.View()
350+ assert.Equal(t, viewBefore, viewAfter)
351+ assert.Equal(t, 5, l.offset)
352+ assert.Equal(t, 33, lipgloss.Height(l.rendered))
353+ golden.RequireEqual(t, []byte(l.View()))
355+ t.Run("should stay at the position it is when the hight of an item below is increased in backwards list", func(t *testing.T) {
359+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
360+ items = append(items, item)
362+ l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item])
363+ execCmd(l, l.Init())
365+ execCmd(l, l.MoveUp(2))
366+ viewBefore := l.View()
368+ execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 29\nLine 2\nLine 3")))
369+ viewAfter := l.View()
370+ assert.Equal(t, viewBefore, viewAfter)
371+ assert.Equal(t, 4, l.offset)
372+ assert.Equal(t, 32, lipgloss.Height(l.rendered))
373+ golden.RequireEqual(t, []byte(l.View()))
375+ t.Run("should stay at the position it is when the hight of an item below is decreases in backwards list", func(t *testing.T) {
379+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
380+ items = append(items, item)
382+ items = append(items, NewSelectableItem("Item 30\nLine 2\nLine 3"))
383+ l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item])
384+ execCmd(l, l.Init())
386+ execCmd(l, l.MoveUp(2))
387+ viewBefore := l.View()
389+ execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 30")))
390+ viewAfter := l.View()
391+ assert.Equal(t, viewBefore, viewAfter)
392+ assert.Equal(t, 0, l.offset)
393+ assert.Equal(t, 31, lipgloss.Height(l.rendered))
394+ golden.RequireEqual(t, []byte(l.View()))
396+ t.Run("should stay at the position it is when the hight of an item above is increased in backwards list", func(t *testing.T) {
400+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
401+ items = append(items, item)
403+ l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item])
404+ execCmd(l, l.Init())
406+ execCmd(l, l.MoveUp(2))
407+ viewBefore := l.View()
409+ execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 1\nLine 2\nLine 3")))
410+ viewAfter := l.View()
411+ assert.Equal(t, viewBefore, viewAfter)
412+ assert.Equal(t, 2, l.offset)
413+ assert.Equal(t, 32, lipgloss.Height(l.rendered))
414+ golden.RequireEqual(t, []byte(l.View()))
416+ t.Run("should stay at the position it is if an item is prepended and we are in backwards list", func(t *testing.T) {
420+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
421+ items = append(items, item)
423+ l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item])
424+ execCmd(l, l.Init())
426+ execCmd(l, l.MoveUp(2))
427+ viewBefore := l.View()
428+ execCmd(l, l.PrependItem(NewSelectableItem("New")))
429+ viewAfter := l.View()
430+ assert.Equal(t, viewBefore, viewAfter)
431+ assert.Equal(t, 2, l.offset)
432+ assert.Equal(t, 31, lipgloss.Height(l.rendered))
433+ golden.RequireEqual(t, []byte(l.View()))
436+ t.Run("should not change offset when new items are prepended and we are at the top in forward list", func(t *testing.T) {
440+ content := strings.Repeat(fmt.Sprintf("Item %d\n", i), i+1)
441+ content = strings.TrimSuffix(content, "\n")
442+ item := NewSelectableItem(content)
443+ items = append(items, item)
445+ l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item])
446+ execCmd(l, l.Init())
447+ execCmd(l, l.PrependItem(NewSelectableItem("Testing")))
449+ assert.Equal(t, 0, l.offset)
450+ golden.RequireEqual(t, []byte(l.View()))
453+ t.Run("should stay at the position it is when new items are added but we moved down in forward list", func(t *testing.T) {
457+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
458+ items = append(items, item)
460+ l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item])
461+ execCmd(l, l.Init())
463+ execCmd(l, l.MoveDown(2))
464+ viewBefore := l.View()
465+ execCmd(l, l.PrependItem(NewSelectableItem("Testing\nHello\n")))
466+ viewAfter := l.View()
467+ assert.Equal(t, viewBefore, viewAfter)
468+ assert.Equal(t, 5, l.offset)
469+ assert.Equal(t, 33, lipgloss.Height(l.rendered))
470+ golden.RequireEqual(t, []byte(l.View()))
473+ t.Run("should stay at the position it is when the hight of an item above is increased in forward list", func(t *testing.T) {
477+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
478+ items = append(items, item)
480+ l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item])
481+ execCmd(l, l.Init())
483+ execCmd(l, l.MoveDown(2))
484+ viewBefore := l.View()
486+ execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 29\nLine 2\nLine 3")))
487+ viewAfter := l.View()
488+ assert.Equal(t, viewBefore, viewAfter)
489+ assert.Equal(t, 4, l.offset)
490+ assert.Equal(t, 32, lipgloss.Height(l.rendered))
491+ golden.RequireEqual(t, []byte(l.View()))
494+ t.Run("should stay at the position it is when the hight of an item above is decreases in forward list", func(t *testing.T) {
497+ items = append(items, NewSelectableItem("At top\nLine 2\nLine 3"))
499+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
500+ items = append(items, item)
502+ l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item])
503+ execCmd(l, l.Init())
505+ execCmd(l, l.MoveDown(3))
506+ viewBefore := l.View()
508+ execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("At top")))
509+ viewAfter := l.View()
510+ assert.Equal(t, viewBefore, viewAfter)
511+ assert.Equal(t, 1, l.offset)
512+ assert.Equal(t, 31, lipgloss.Height(l.rendered))
513+ golden.RequireEqual(t, []byte(l.View()))
516+ t.Run("should stay at the position it is when the hight of an item below is increased in forward list", func(t *testing.T) {
520+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
521+ items = append(items, item)
523+ l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item])
524+ execCmd(l, l.Init())
526+ execCmd(l, l.MoveDown(2))
527+ viewBefore := l.View()
529+ execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 29\nLine 2\nLine 3")))
530+ viewAfter := l.View()
531+ assert.Equal(t, viewBefore, viewAfter)
532+ assert.Equal(t, 2, l.offset)
533+ assert.Equal(t, 32, lipgloss.Height(l.rendered))
534+ golden.RequireEqual(t, []byte(l.View()))
536+ t.Run("should stay at the position it is if an item is appended and we are in forward list", func(t *testing.T) {
540+ item := NewSelectableItem(fmt.Sprintf("Item %d", i))
541+ items = append(items, item)
543+ l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item])
544+ execCmd(l, l.Init())
546+ execCmd(l, l.MoveDown(2))
547+ viewBefore := l.View()
548+ execCmd(l, l.AppendItem(NewSelectableItem("New")))
549+ viewAfter := l.View()
550+ assert.Equal(t, viewBefore, viewAfter)
551+ assert.Equal(t, 2, l.offset)
552+ assert.Equal(t, 31, lipgloss.Height(l.rendered))
553+ golden.RequireEqual(t, []byte(l.View()))
320type SelectableItem interface {
557type SelectableItem interface {