diff --git a/table/target_height_test.go b/table/target_height_test.go index d87df55..a597ed8 100644 --- a/table/target_height_test.go +++ b/table/target_height_test.go @@ -396,6 +396,37 @@ func TestTargetHeightVisibleDataLinesWithSeparator(t *testing.T) { assert.Greater(t, len(lines), 0, "table should not be empty") } +// TestTargetHeightWithRowBorderNoMultilineRespectsHeight checks that enabling +// WithRowBorder on a non-multiline table does not cause the rendered output to +// exceed targetHeight when WithMinimumHeight is also set. Previously, +// visibleDataLines ignored separator lines in non-multiline mode, so +// calculatePadding added too many blank rows and the table overflowed. +func TestTargetHeightWithRowBorderNoMultilineRespectsHeight(t *testing.T) { + rows := []Row{ + shortRow(1), + shortRow(2), + shortRow(3), + } + + const target = 12 + + model := New([]Column{ + NewColumn("id", "ID", 3), + NewColumn("content", "Content", 20), + }). + WithRows(rows). + WithRowBorder(true). + WithTargetHeight(target). + WithMinimumHeight(target) + + rendered := model.View() + lines := strings.Split(rendered, "\n") + + assert.LessOrEqual(t, len(lines), target, + "rendered height (%d lines) exceeded target (%d)", len(lines), target) + assert.Greater(t, len(lines), 0, "table should not be empty") +} + // TestTargetHeightNoFooterOnSinglePage checks that no page-count footer is // rendered when all rows fit on a single page. func TestTargetHeightNoFooterOnSinglePage(t *testing.T) { diff --git a/table/view.go b/table/view.go index 6f87a21..4569297 100644 --- a/table/view.go +++ b/table/view.go @@ -7,12 +7,21 @@ import ( ) // visibleDataLines returns the total terminal lines occupied by visible rows in -// the range [startRowIndex, endRowIndex]. When multiline is disabled every row -// is one line, so the result equals endRowIndex - startRowIndex + 1. +// the range [startRowIndex, endRowIndex], including any separator lines between +// rows when WithRowBorder is active. func (m *Model) visibleDataLines(startRowIndex, endRowIndex int) int { numRows := endRowIndex - startRowIndex + 1 - if !m.multiline || numRows <= 0 { + if numRows <= 0 { + return numRows + } + + if !m.multiline { + if m.rowSeparator { + // Each pair of adjacent rows has one separator line between them. + return 2*numRows - 1 + } + return numRows } diff --git a/table/view_test.go b/table/view_test.go index 20ca47c..f3d47f5 100644 --- a/table/view_test.go +++ b/table/view_test.go @@ -1959,9 +1959,10 @@ func TestOuterBorderTrueMatchesDefault(t *testing.T) { } func TestRowBorderNoSeparatorBeforePaddingRows(t *testing.T) { - // WithMinimumHeight(7) with 2 data rows produces 1 padding row. - // The separator should appear between the two data rows but NOT - // between the last data row and the blank padding row. + // WithMinimumHeight(7) with 2 data rows and 1 separator between them + // occupies 3 data lines; header=3, bottom border=1 → total=7=minimumHeight. + // No blank padding row is needed because the separators are now counted + // as data lines, so the budget is already satisfied. model := New([]Column{ NewColumn("1", "1", 4), NewColumn("2", "2", 4), @@ -1976,7 +1977,6 @@ func TestRowBorderNoSeparatorBeforePaddingRows(t *testing.T) { ┃ a┃ b┃ ┣━━━━╋━━━━┫ ┃ c┃ d┃ -┃ ┃ ┃ ┗━━━━┻━━━━┛` rendered := model.View()