Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions borders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,105 @@ func maxRuneWidthOld(str string) int {

return width
}

func TestGetBorderSidesWithImplicitBorders(t *testing.T) {
// When using BorderStyle() without explicitly setting sides,
// all sides should be implicitly enabled (matching render behavior).
// See: https://github.com/charmbracelet/lipgloss/issues/522
t.Run("BorderStyle only (implicit borders)", func(t *testing.T) {
s := NewStyle().BorderStyle(NormalBorder())

if !s.GetBorderTop() {
t.Error("GetBorderTop() = false, want true (implicit border)")
}
if !s.GetBorderRight() {
t.Error("GetBorderRight() = false, want true (implicit border)")
}
if !s.GetBorderBottom() {
t.Error("GetBorderBottom() = false, want true (implicit border)")
}
if !s.GetBorderLeft() {
t.Error("GetBorderLeft() = false, want true (implicit border)")
}

_, top, right, bottom, left := s.GetBorder()
if !top || !right || !bottom || !left {
t.Errorf("GetBorder() sides = (%v, %v, %v, %v), want all true", top, right, bottom, left)
}
})

// When sides are explicitly set, implicit borders should not interfere.
t.Run("Border with explicit sides", func(t *testing.T) {
s := NewStyle().Border(NormalBorder(), true, false, true, false)

if !s.GetBorderTop() {
t.Error("GetBorderTop() = false, want true")
}
if s.GetBorderRight() {
t.Error("GetBorderRight() = true, want false")
}
if !s.GetBorderBottom() {
t.Error("GetBorderBottom() = false, want true")
}
if s.GetBorderLeft() {
t.Error("GetBorderLeft() = true, want false")
}
})

// When no border is set at all, all getters should return false.
t.Run("No border set", func(t *testing.T) {
s := NewStyle()

if s.GetBorderTop() {
t.Error("GetBorderTop() = true, want false")
}
if s.GetBorderRight() {
t.Error("GetBorderRight() = true, want false")
}
if s.GetBorderBottom() {
t.Error("GetBorderBottom() = true, want false")
}
if s.GetBorderLeft() {
t.Error("GetBorderLeft() = true, want false")
}
})

// Frame size should be consistent with border side getters.
t.Run("Frame size consistency with BorderStyle only", func(t *testing.T) {
s := NewStyle().BorderStyle(NormalBorder()).Padding(1, 2)

// GetHorizontalFrameSize should include both border and padding
hFrame := s.GetHorizontalFrameSize()
wantH := 2 + 4 // 2 for left+right border, 4 for left+right padding
if hFrame != wantH {
t.Errorf("GetHorizontalFrameSize() = %d, want %d", hFrame, wantH)
}

vFrame := s.GetVerticalFrameSize()
wantV := 2 + 2 // 2 for top+bottom border, 2 for top+bottom padding
if vFrame != wantV {
t.Errorf("GetVerticalFrameSize() = %d, want %d", vFrame, wantV)
}
})

// Once a side is explicitly set, implicitBorders is no longer in effect.
t.Run("BorderStyle then explicit side disables implicit", func(t *testing.T) {
s := NewStyle().BorderStyle(NormalBorder()).BorderTop(true)

// Top was explicitly set to true
if !s.GetBorderTop() {
t.Error("GetBorderTop() = false, want true")
}
// Other sides should be false because at least one side was explicitly set,
// so implicit borders no longer applies.
if s.GetBorderRight() {
t.Error("GetBorderRight() = true, want false (implicit borders disabled)")
}
if s.GetBorderBottom() {
t.Error("GetBorderBottom() = true, want false (implicit borders disabled)")
}
if s.GetBorderLeft() {
t.Error("GetBorderLeft() = true, want false (implicit borders disabled)")
}
})
}
37 changes: 29 additions & 8 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,17 @@ func (s Style) GetVerticalMargins() int {
// top, right, bottom, and left in that order. If no value is set for the
// border style, Border{} is returned. For all other unset values false is
// returned.
//
// Note: if a border style has been set via [Style.BorderStyle] without
// explicitly enabling or disabling individual sides, all sides are considered
// implicitly enabled (matching the rendering behavior).
func (s Style) GetBorder() (b Border, top, right, bottom, left bool) {
implicit := s.implicitBorders()
return s.getBorderStyle(),
s.getAsBool(borderTopKey, false),
s.getAsBool(borderRightKey, false),
s.getAsBool(borderBottomKey, false),
s.getAsBool(borderLeftKey, false)
s.getAsBool(borderTopKey, false) || implicit,
s.getAsBool(borderRightKey, false) || implicit,
s.getAsBool(borderBottomKey, false) || implicit,
s.getAsBool(borderLeftKey, false) || implicit
}

// GetBorderStyle returns the style's border style (type Border). If no value
Expand All @@ -217,26 +222,42 @@ func (s Style) GetBorderStyle() Border {

// GetBorderTop returns the style's top border setting. If no value is set
// false is returned.
//
// Note: if a border style has been set via [Style.BorderStyle] without
// explicitly enabling or disabling individual sides, all sides are considered
// implicitly enabled (matching the rendering behavior).
func (s Style) GetBorderTop() bool {
return s.getAsBool(borderTopKey, false)
return s.getAsBool(borderTopKey, false) || s.implicitBorders()
}

// GetBorderRight returns the style's right border setting. If no value is set
// false is returned.
//
// Note: if a border style has been set via [Style.BorderStyle] without
// explicitly enabling or disabling individual sides, all sides are considered
// implicitly enabled (matching the rendering behavior).
func (s Style) GetBorderRight() bool {
return s.getAsBool(borderRightKey, false)
return s.getAsBool(borderRightKey, false) || s.implicitBorders()
}

// GetBorderBottom returns the style's bottom border setting. If no value is
// set false is returned.
//
// Note: if a border style has been set via [Style.BorderStyle] without
// explicitly enabling or disabling individual sides, all sides are considered
// implicitly enabled (matching the rendering behavior).
func (s Style) GetBorderBottom() bool {
return s.getAsBool(borderBottomKey, false)
return s.getAsBool(borderBottomKey, false) || s.implicitBorders()
}

// GetBorderLeft returns the style's left border setting. If no value is
// set false is returned.
//
// Note: if a border style has been set via [Style.BorderStyle] without
// explicitly enabling or disabling individual sides, all sides are considered
// implicitly enabled (matching the rendering behavior).
func (s Style) GetBorderLeft() bool {
return s.getAsBool(borderLeftKey, false)
return s.getAsBool(borderLeftKey, false) || s.implicitBorders()
}

// GetBorderTopForeground returns the style's border top foreground color. If
Expand Down
10 changes: 10 additions & 0 deletions style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,16 @@ func TestStyleUnset(t *testing.T) {

requireTrue(t, s.GetBorderLeft())
s = s.UnsetBorderLeft()
// After unsetting all four sides with the border style still set,
// implicit borders kick in (matching the rendering behavior).
// To fully disable borders, unset the border style as well.
requireTrue(t, s.GetBorderLeft())
s = s.UnsetBorderStyle()
requireFalse(t, s.GetBorderLeft())

// Explicitly setting a side to false (as opposed to unsetting) should
// disable that side and prevent implicit borders from applying.
s = NewStyle().Border(normalBorder, true, true, true, false)
requireFalse(t, s.GetBorderLeft())

// tab width
Expand Down