diff --git a/borders_test.go b/borders_test.go index 5af87819..9f59f676 100644 --- a/borders_test.go +++ b/borders_test.go @@ -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)") + } + }) +} diff --git a/get.go b/get.go index 146462f2..272ef1c6 100644 --- a/get.go +++ b/get.go @@ -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 @@ -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 diff --git a/style_test.go b/style_test.go index 0db740b4..e3bac8d0 100644 --- a/style_test.go +++ b/style_test.go @@ -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