Repository: Evertras/bubble-table
Pull request #97 (2022-06-11 00:49:37)

Add queries for selected row, scrolling, and focused
Improves #60 

Adds query functions for current highlighted row, whether the table is focused or not, and the current horizontal scroll offset.

Moves `GetVisibleRows` into the query file for consistency, but functionality is unchanged.


Patch:
diff --git a/table/model.go b/table/model.go
index da42878..f832b4c 100644
--- a/table/model.go
+++ b/table/model.go
@@ -100,15 +100,3 @@ func New(columns []Column) Model {
 func (m Model) Init() tea.Cmd {
 	return nil
 }
-
-// GetVisibleRows return sorted and filtered rows.
-func (m Model) GetVisibleRows() []Row {
-	rows := make([]Row, len(m.rows))
-	copy(rows, m.rows)
-	if m.filtered {
-		rows = m.getFilteredRows(rows)
-	}
-	rows = getSortedRows(m.sortOrder, rows)
-
-	return rows
-}
diff --git a/table/query.go b/table/query.go
index 06df583..7564178 100644
--- a/table/query.go
+++ b/table/query.go
@@ -30,3 +30,33 @@ func (m *Model) GetIsFilterActive() bool {
 func (m *Model) GetCurrentFilter() string {
 	return m.filterTextInput.Value()
 }
+
+// GetVisibleRows returns sorted and filtered rows.
+func (m Model) GetVisibleRows() []Row {
+	rows := make([]Row, len(m.rows))
+	copy(rows, m.rows)
+	if m.filtered {
+		rows = m.getFilteredRows(rows)
+	}
+	rows = getSortedRows(m.sortOrder, rows)
+
+	return rows
+}
+
+// GetHighlightedRowIndex returns the index of the Row that's currently highlighted
+// by the user.
+func (m *Model) GetHighlightedRowIndex() int {
+	return m.rowCursorIndex
+}
+
+// GetFocused returns whether or not the table is focused and is receiving inputs.
+func (m *Model) GetFocused() bool {
+	return m.focused
+}
+
+// GetHorizontalScrollColumnOffset returns how many columns to the right the table
+// has been scrolled.  0 means the table is all the way to the left, which is
+// the starting default.
+func (m *Model) GetHorizontalScrollColumnOffset() int {
+	return m.horizontalScrollOffsetCol
+}


Test patch:
diff --git a/table/model_test.go b/table/model_test.go
index 1864f22..531526e 100644
--- a/table/model_test.go
+++ b/table/model_test.go
@@ -3,7 +3,6 @@ package table
 import (
 	"testing"
 
-	"github.com/charmbracelet/bubbles/textinput"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -14,26 +13,3 @@ func TestModelInitReturnsNil(t *testing.T) {
 
 	assert.Nil(t, cmd)
 }
-
-func TestGetVisibleRows(t *testing.T) {
-	input := textinput.Model{}
-	input.SetValue("AAA")
-	columns := []Column{NewColumn("title", "title", 10).WithFiltered(true)}
-	rows := []Row{
-		NewRow(RowData{
-			"title":       "AAA",
-			"description": "",
-		}),
-		NewRow(RowData{
-			"title":       "BBB",
-			"description": "",
-		}),
-		NewRow(RowData{
-			"title":       "CCC",
-			"description": "",
-		}),
-	}
-	m := Model{filtered: true, filterTextInput: input, columns: columns, rows: rows}
-	visibleRows := m.GetVisibleRows()
-	assert.Len(t, visibleRows, 1)
-}
diff --git a/table/query_test.go b/table/query_test.go
index 30f2cac..85f9aa6 100644
--- a/table/query_test.go
+++ b/table/query_test.go
@@ -3,6 +3,8 @@ package table
 import (
 	"testing"
 
+	"github.com/charmbracelet/bubbles/textinput"
+	tea "github.com/charmbracelet/bubbletea"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -50,3 +52,111 @@ func TestGetFilterData(t *testing.T) {
 	assert.True(t, model.GetCanFilter(), "Filter feature should be enabled")
 	assert.Equal(t, model.GetCurrentFilter(), "a", "Filter string should be what was typed")
 }
+
+func TestGetVisibleRows(t *testing.T) {
+	input := textinput.Model{}
+	input.SetValue("AAA")
+	columns := []Column{NewColumn("title", "title", 10).WithFiltered(true)}
+	rows := []Row{
+		NewRow(RowData{
+			"title":       "AAA",
+			"description": "",
+		}),
+		NewRow(RowData{
+			"title":       "BBB",
+			"description": "",
+		}),
+		NewRow(RowData{
+			"title":       "CCC",
+			"description": "",
+		}),
+	}
+	m := Model{filtered: true, filterTextInput: input, columns: columns, rows: rows}
+	visibleRows := m.GetVisibleRows()
+	assert.Len(t, visibleRows, 1)
+}
+
+func TestGetHighlightedRowIndex(t *testing.T) {
+	model := New([]Column{})
+
+	assert.Equal(t, 0, model.GetHighlightedRowIndex(), "Empty table should still safely have 0 index highlighted")
+
+	// We don't actually need data to test this
+	empty := RowData{}
+	model = model.WithRows([]Row{NewRow(empty), NewRow(empty)})
+
+	assert.Equal(t, 0, model.GetHighlightedRowIndex(), "Unfocused table should start with 0 index")
+
+	model = model.WithHighlightedRow(1)
+
+	assert.Equal(t, 1, model.GetHighlightedRowIndex(), "Table with set highlighted row should return same highlighted row")
+}
+
+func TestGetFocused(t *testing.T) {
+	model := New([]Column{})
+
+	assert.Equal(t, false, model.GetFocused(), "Table should not be focused by default")
+
+	model = model.Focused(true)
+
+	assert.Equal(t, true, model.GetFocused(), "Table should be focused after being set")
+}
+
+func TestGetHorizontalScrollColumnOffset(t *testing.T) {
+	model := New([]Column{
+		NewColumn("1", "1", 4),
+		NewColumn("2", "2", 4),
+		NewColumn("3", "3", 4),
+		NewColumn("4", "4", 4),
+	}).
+		WithRows([]Row{
+			NewRow(RowData{
+				"1": "x1",
+				"2": "x2",
+				"3": "x3",
+				"4": "x4",
+			}),
+		}).
+		WithMaxTotalWidth(18).
+		Focused(true)
+
+	hitScrollRight := func() {
+		model, _ = model.Update(tea.KeyMsg{Type: tea.KeyShiftRight})
+	}
+
+	hitScrollLeft := func() {
+		model, _ = model.Update(tea.KeyMsg{Type: tea.KeyShiftLeft})
+	}
+
+	assert.Equal(
+		t,
+		0,
+		model.GetHorizontalScrollColumnOffset(),
+		"Should start to left",
+	)
+
+	hitScrollRight()
+
+	assert.Equal(
+		t,
+		1,
+		model.GetHorizontalScrollColumnOffset(),
+		"Should be 1 after scrolling to the right once",
+	)
+
+	hitScrollLeft()
+	assert.Equal(
+		t,
+		0,
+		model.GetHorizontalScrollColumnOffset(),
+		"Should be back to 0 after moving to the left",
+	)
+
+	hitScrollLeft()
+	assert.Equal(
+		t,
+		0,
+		model.GetHorizontalScrollColumnOffset(),
+		"Should still be 0 after trying to go left again",
+	)
+}