diff --git a/CHANGELOG.md b/CHANGELOG.md
index aba3ff935..153928d2b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## Unreleased
+
+### Changed
+
+- Allowed border_title and border_subtitle to accept Text objects
+- Added additional line around titles
+
## [0.18.0] - 2023-04-04
### Added
diff --git a/src/textual/_border.py b/src/textual/_border.py
index 5fe76cc3b..3b3bf2670 100644
--- a/src/textual/_border.py
+++ b/src/textual/_border.py
@@ -283,7 +283,7 @@ def get_box(
def render_border_label(
- label: str,
+ label: Text,
is_title: bool,
name: EdgeType,
width: int,
@@ -323,12 +323,10 @@ def render_border_label(
# How many cells do we need to reserve for surrounding blanks and corners?
corners_needed = has_left_corner + has_right_corner
cells_reserved = 2 * corners_needed
- if not label or width <= cells_reserved:
+ if not label.cell_len or width <= cells_reserved:
return
- text_label = Text.from_markup(label)
- if not text_label.cell_len:
- return
+ text_label = label.copy()
text_label.truncate(width - cells_reserved, overflow="ellipsis")
segments = text_label.render(console)
@@ -401,13 +399,15 @@ def render_row(
elif not label_length:
yield Segment(box2.text * space_available, box2.style)
elif label_alignment == "left" or label_alignment == "right":
- edge = Segment(box2.text * space_available, box2.style)
+ edge = Segment(box2.text * (space_available - 1), box2.style)
if label_alignment == "left":
+ yield Segment(box2.text, box2.style)
yield from label_segments_list
yield edge
else:
yield edge
yield from label_segments_list
+ yield Segment(box2.text, box2.style)
elif label_alignment == "center":
length_on_left = space_available // 2
length_on_right = space_available - length_on_left
diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py
index c66149717..8092f0c45 100644
--- a/src/textual/_styles_cache.py
+++ b/src/textual/_styles_cache.py
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Callable, Iterable
from rich.console import Console
from rich.segment import Segment
from rich.style import Style
+from rich.text import Text
from ._border import get_box, render_border_label, render_row
from ._opacity import _apply_opacity
@@ -115,8 +116,8 @@ class StylesCache:
background,
widget.render_line,
widget.app.console,
- widget.border_title,
- widget.border_subtitle,
+ widget._border_title,
+ widget._border_subtitle,
content_size=widget.content_region.size,
padding=styles.padding,
crop=crop,
@@ -146,8 +147,8 @@ class StylesCache:
background: Color,
render_content_line: RenderLineCallback,
console: Console,
- border_title: str,
- border_subtitle: str,
+ border_title: Text | None,
+ border_subtitle: Text | None,
content_size: Size | None = None,
padding: Spacing | None = None,
crop: Region | None = None,
@@ -228,8 +229,8 @@ class StylesCache:
background: Color,
render_content_line: Callable[[int], Strip],
console: Console,
- border_title: str,
- border_subtitle: str,
+ border_title: Text | None,
+ border_subtitle: Text | None,
) -> Strip:
"""Render a styled line.
@@ -310,7 +311,7 @@ class StylesCache:
border_label,
is_top,
border_edge_type,
- width,
+ width - 2,
inner,
outer,
border_color_as_style,
diff --git a/src/textual/app.py b/src/textual/app.py
index aeba923df..647a805f7 100644
--- a/src/textual/app.py
+++ b/src/textual/app.py
@@ -1011,6 +1011,8 @@ class App(Generic[ReturnType], DOMNode):
)
try:
+ app._loop = asyncio.get_running_loop()
+ app._thread_id = threading.get_ident()
await app._process_messages(
ready_callback=None if auto_pilot is None else app_ready,
headless=headless,
diff --git a/src/textual/widget.py b/src/textual/widget.py
index d387523cb..fc5c8fb62 100644
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -188,6 +188,34 @@ class PseudoClasses(NamedTuple):
hover: bool
+class _BorderTitle:
+ """Descriptor to set border titles."""
+
+ def __set_name__(self, owner: Widget, name: str) -> None:
+ # The private name where we store the real data.
+ self._internal_name = f"_{name}"
+
+ def __set__(self, obj: Widget, title: str | Text | None) -> None:
+ """Setting a title accepts a str, Text, or None."""
+ if title is None:
+ setattr(obj, self._internal_name, None)
+ else:
+ # We store the title as Text
+ new_title = obj.render_str(title)
+ new_title.expand_tabs(4)
+ new_title = new_title.split()[0]
+ setattr(obj, self._internal_name, new_title)
+ obj.refresh()
+
+ def __get__(self, obj: Widget, objtype: type[Widget] | None = None) -> str | None:
+ """Getting a title will return None or a str as console markup."""
+ title: Text | None = getattr(obj, self._internal_name, None)
+ if title is None:
+ return None
+ # If we have a title, convert from Text to console markup
+ return title.markup
+
+
@rich.repr.auto
class Widget(DOMNode):
"""
@@ -241,10 +269,6 @@ class Widget(DOMNode):
"""Widget will highlight links automatically."""
disabled = Reactive(False)
"""The disabled state of the widget. `True` if disabled, `False` if not."""
- border_title = Reactive("")
- """The one-line border title, which may contain markup to be parsed."""
- border_subtitle = Reactive("")
- """The one-line border subtitle, which may contain markup to be parsed."""
hover_style: Reactive[Style] = Reactive(Style, repaint=False)
highlight_link_id: Reactive[str] = Reactive("")
@@ -270,6 +294,9 @@ class Widget(DOMNode):
self._horizontal_scrollbar: ScrollBar | None = None
self._scrollbar_corner: ScrollBarCorner | None = None
+ self._border_title: Text | None = None
+ self._border_subtitle: Text | None = None
+
self._render_cache = RenderCache(Size(0, 0), [])
# Regions which need to be updated (in Widget)
self._dirty_regions: set[Region] = set()
@@ -321,6 +348,11 @@ class Widget(DOMNode):
show_vertical_scrollbar = Reactive(False, layout=True)
show_horizontal_scrollbar = Reactive(False, layout=True)
+ border_title = _BorderTitle()
+ """A title to show in the top border (if there is one)."""
+ border_subtitle = _BorderTitle()
+ """A title to show in the bottom border (if there is one)."""
+
@property
def siblings(self) -> list[Widget]:
"""Get the widget's siblings (self is removed from the return list).
@@ -2429,20 +2461,6 @@ class Widget(DOMNode):
"""Update the styles of the widget and its children when disabled is toggled."""
self._update_styles()
- def validate_border_title(self, title: str) -> str:
- """Ensure we only use a single line for the border title."""
- if not title:
- return title
- first, *_ = title.splitlines()
- return first
-
- def validate_border_subtitle(self, subtitle: str) -> str:
- """Ensure we only use a single line for the border subtitle."""
- if not subtitle:
- return subtitle
- first, *_ = subtitle.splitlines()
- return first
-
def _size_updated(
self, size: Size, virtual_size: Size, container_size: Size, layout: bool = True
) -> bool:
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index 49bcb024b..11b326d4f 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -3113,141 +3113,141 @@
font-weight: 700;
}
- .terminal-3789115238-matrix {
+ .terminal-3714773288-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-3789115238-title {
+ .terminal-3714773288-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-3789115238-r1 { fill: #e1e1e1 }
- .terminal-3789115238-r2 { fill: #c5c8c6 }
- .terminal-3789115238-r3 { fill: #fea62b }
- .terminal-3789115238-r4 { fill: #fea62b;font-weight: bold }
- .terminal-3789115238-r5 { fill: #fea62b;font-weight: bold;font-style: italic; }
- .terminal-3789115238-r6 { fill: #cc555a;font-weight: bold }
- .terminal-3789115238-r7 { fill: #1e1e1e }
- .terminal-3789115238-r8 { fill: #1e1e1e;text-decoration: underline; }
- .terminal-3789115238-r9 { fill: #fea62b;text-decoration: underline; }
- .terminal-3789115238-r10 { fill: #4b4e55;text-decoration: underline; }
- .terminal-3789115238-r11 { fill: #4ebf71 }
- .terminal-3789115238-r12 { fill: #b93c5b }
+ .terminal-3714773288-r1 { fill: #e1e1e1 }
+ .terminal-3714773288-r2 { fill: #c5c8c6 }
+ .terminal-3714773288-r3 { fill: #fea62b }
+ .terminal-3714773288-r4 { fill: #fea62b;font-weight: bold }
+ .terminal-3714773288-r5 { fill: #fea62b;font-weight: bold;font-style: italic; }
+ .terminal-3714773288-r6 { fill: #cc555a;font-weight: bold }
+ .terminal-3714773288-r7 { fill: #1e1e1e }
+ .terminal-3714773288-r8 { fill: #1e1e1e;text-decoration: underline; }
+ .terminal-3714773288-r9 { fill: #fea62b;text-decoration: underline; }
+ .terminal-3714773288-r10 { fill: #4b4e55;text-decoration: underline; }
+ .terminal-3714773288-r11 { fill: #4ebf71 }
+ .terminal-3714773288-r12 { fill: #b93c5b }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- BorderSubTitleAlignAll
+ BorderSubTitleAlignAll
-
-
-
-
-
- ▏Border title▕╭Left,…╮▁▁▁▁▁Left▁▁▁▁▁
- ▏This is the story of▕│a Python│▎developer that▋
- ▏Border subtitle▕╰Cente…╯▔▔▔▔▔@@@▔▔▔▔▔▔
-
-
-
-
-
- +--------------+Title──────────────────
- |had to fill up|nine labelsand ended up redoing it
- +Left--------+───────────────Subtitle
-
-
-
-
- Title, but really looooo…
- Title, but rea…Title, but really …
- because the first tryhad some labelsthat were too long.
- Subtitle, but …Subtitle, but real…
- Subtitle, but really loo…
-
+
+
+
+
+
+ ▏Border title▕╭─Lef…─╮▁▁▁▁▁Left▁▁▁▁▁
+ ▏This is the story of▕│a Python│▎developer that▋
+ ▏Border subtitle▕╰─Cen…─╯▔▔▔▔▔@@@▔▔▔▔▔▔
+
+
+
+
+
+ +--------------+─Title─────────────────
+ |had to fill up|nine labelsand ended up redoing it
+ +-Left-------+──────────────Subtitle─
+
+
+
+
+ ─Title, but really looo…─
+ ─Title, but r…──Title, but reall…─
+ because the first tryhad some labelsthat were too long.
+ ─Subtitle, bu…──Subtitle, but re…─
+ ─Subtitle, but really l…─
+
@@ -3278,134 +3278,134 @@
font-weight: 700;
}
- .terminal-1944504807-matrix {
+ .terminal-1044533580-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-1944504807-title {
+ .terminal-1044533580-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-1944504807-r1 { fill: #e1e1e1 }
- .terminal-1944504807-r2 { fill: #c5c8c6 }
- .terminal-1944504807-r3 { fill: #fea62b }
- .terminal-1944504807-r4 { fill: #ffffff }
- .terminal-1944504807-r5 { fill: #1e1e1e }
+ .terminal-1044533580-r1 { fill: #e1e1e1 }
+ .terminal-1044533580-r2 { fill: #c5c8c6 }
+ .terminal-1044533580-r3 { fill: #fea62b }
+ .terminal-1044533580-r4 { fill: #ffffff }
+ .terminal-1044533580-r5 { fill: #1e1e1e }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- BorderSubtitleAlignApp
+ BorderSubtitleAlignApp
-
-
-
-
- ┌────────────────────────────────────────────────────────────────────────────┐
- ││
- │My subtitle is on the left.│
- ││
- └< Left────────────────────────────────────────────────────────────────────┘
-
- ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
- ╏╏
- ╏My subtitle is centered╏
- ╏╏
- ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍Centered!╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
-
- ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
- ▊▎
- ▊My subtitle is on the right▎
- ▊▎
- ▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Right >▎
-
-
-
-
-
+
+
+
+
+ ┌────────────────────────────────────────────────────────────────────────────┐
+ ││
+ │My subtitle is on the left.│
+ ││
+ └─< Left───────────────────────────────────────────────────────────────────┘
+
+ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
+ ╏╏
+ ╏My subtitle is centered╏
+ ╏╏
+ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍Centered!╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
+
+ ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
+ ▊▎
+ ▊My subtitle is on the right▎
+ ▊▎
+ ▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁Right >▁▎
+
+
+
+
+
@@ -3436,134 +3436,134 @@
font-weight: 700;
}
- .terminal-1060422964-matrix {
+ .terminal-304237721-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-1060422964-title {
+ .terminal-304237721-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-1060422964-r1 { fill: #e1e1e1 }
- .terminal-1060422964-r2 { fill: #c5c8c6 }
- .terminal-1060422964-r3 { fill: #fea62b }
- .terminal-1060422964-r4 { fill: #ffffff }
- .terminal-1060422964-r5 { fill: #1e1e1e }
+ .terminal-304237721-r1 { fill: #e1e1e1 }
+ .terminal-304237721-r2 { fill: #c5c8c6 }
+ .terminal-304237721-r3 { fill: #fea62b }
+ .terminal-304237721-r4 { fill: #ffffff }
+ .terminal-304237721-r5 { fill: #1e1e1e }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- BorderTitleAlignApp
+ BorderTitleAlignApp
-
-
-
-
- ┌< Left────────────────────────────────────────────────────────────────────┐
- ││
- │My title is on the left.│
- ││
- └────────────────────────────────────────────────────────────────────────────┘
-
- ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍Centered!╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
- ╏╏
- ╏My title is centered╏
- ╏╏
- ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
-
- ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔Right >▎
- ▊▎
- ▊My title is on the right▎
- ▊▎
- ▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
-
-
-
-
-
+
+
+
+
+ ┌─< Left───────────────────────────────────────────────────────────────────┐
+ ││
+ │My title is on the left.│
+ ││
+ └────────────────────────────────────────────────────────────────────────────┘
+
+ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍Centered!╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
+ ╏╏
+ ╏My title is centered╏
+ ╏╏
+ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
+
+ ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔Right >▔▎
+ ▊▎
+ ▊My title is on the right▎
+ ▊▎
+ ▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
+
+
+
+
+
@@ -19539,134 +19539,134 @@
font-weight: 700;
}
- .terminal-1364835124-matrix {
+ .terminal-2452225303-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-1364835124-title {
+ .terminal-2452225303-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-1364835124-r1 { fill: #c5c8c6 }
- .terminal-1364835124-r2 { fill: #e3e3e3 }
- .terminal-1364835124-r3 { fill: #ff0000 }
- .terminal-1364835124-r4 { fill: #dde2e8 }
- .terminal-1364835124-r5 { fill: #ddedf9 }
+ .terminal-2452225303-r1 { fill: #c5c8c6 }
+ .terminal-2452225303-r2 { fill: #e3e3e3 }
+ .terminal-2452225303-r3 { fill: #ff0000 }
+ .terminal-2452225303-r4 { fill: #dde2e8 }
+ .terminal-2452225303-r5 { fill: #ddedf9 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- ScrollViewTester
+ ScrollViewTester
-
-
-
- ⭘ScrollViewTester
- ╭1───────────────────────────────────────────────────────────────────────────╮
- │Welcome to line 980│
- │Welcome to line 981│
- │Welcome to line 982│
- │Welcome to line 983│
- │Welcome to line 984│
- │Welcome to line 985│
- │Welcome to line 986│
- │Welcome to line 987│
- │Welcome to line 988│
- │Welcome to line 989│
- │Welcome to line 990│
- │Welcome to line 991│
- │Welcome to line 992│
- │Welcome to line 993│
- │Welcome to line 994│
- │Welcome to line 995│
- │Welcome to line 996│
- │Welcome to line 997│
- │Welcome to line 998│
- │Welcome to line 999│
- ╰──────────────────────────────────────────────────────────────────────────────╯
+
+
+
+ ⭘ScrollViewTester
+ ╭─1──────────────────────────────────────────────────────────────────────────╮
+ │Welcome to line 980│
+ │Welcome to line 981│
+ │Welcome to line 982│
+ │Welcome to line 983│
+ │Welcome to line 984│
+ │Welcome to line 985│
+ │Welcome to line 986│
+ │Welcome to line 987│
+ │Welcome to line 988│
+ │Welcome to line 989│
+ │Welcome to line 990│
+ │Welcome to line 991│
+ │Welcome to line 992│
+ │Welcome to line 993│
+ │Welcome to line 994│
+ │Welcome to line 995│
+ │Welcome to line 996│
+ │Welcome to line 997│
+ │Welcome to line 998│
+ │Welcome to line 999│
+ ╰──────────────────────────────────────────────────────────────────────────────╯
diff --git a/tests/test_border.py b/tests/test_border.py
index 5cae0d0aa..fe02d5e28 100644
--- a/tests/test_border.py
+++ b/tests/test_border.py
@@ -2,6 +2,7 @@ import pytest
from rich.console import Console
from rich.segment import Segment
from rich.style import Style
+from rich.text import Text
from textual._border import render_border_label, render_row
from textual.widget import Widget
@@ -37,6 +38,11 @@ def test_border_title_single_line():
"""The border_title gets set to a single line even when multiple lines are provided."""
widget = Widget()
+ assert widget.border_title is None
+
+ widget.border_title = None
+ assert widget.border_title == None
+
widget.border_title = ""
assert widget.border_title == ""
@@ -50,7 +56,10 @@ def test_border_title_single_line():
assert widget.border_title == "Sorry you "
widget.border_title = "[red]This also \n works with markup \n involved.[/]"
- assert widget.border_title == "[red]This also "
+ assert widget.border_title == "[red]This also [/red]"
+
+ widget.border_title = Text.from_markup("[bold]Hello World")
+ assert widget.border_title == "[bold]Hello World[/bold]"
def test_border_subtitle_single_line():
@@ -70,7 +79,10 @@ def test_border_subtitle_single_line():
assert widget.border_subtitle == "Sorry you "
widget.border_subtitle = "[red]This also \n works with markup \n involved.[/]"
- assert widget.border_subtitle == "[red]This also "
+ assert widget.border_subtitle == "[red]This also [/red]"
+
+ widget.border_subtitle = Text.from_markup("[bold]Hello World")
+ assert widget.border_subtitle == "[bold]Hello World[/bold]"
@pytest.mark.parametrize(
@@ -93,7 +105,7 @@ def test_render_border_label_empty_label_skipped(
assert [] == list(
render_border_label(
- "",
+ Text(""),
True,
"round",
width,
@@ -130,7 +142,7 @@ def test_render_border_label_skipped_if_narrow(
assert [] == list(
render_border_label(
- label,
+ Text.from_markup(label),
True,
"round",
width,
@@ -168,7 +180,7 @@ def test_render_border_label_wide_plain(label: str):
True,
True,
)
- left, original_text, right = render_border_label(label, *args)
+ left, original_text, right = render_border_label(Text.from_markup(label), *args)
assert left == _BLANK_SEGMENT
assert right == _BLANK_SEGMENT
@@ -188,7 +200,7 @@ def test_render_border_empty_text_with_markup(label: str):
"""Test label rendering if there is no text but some markup."""
assert [] == list(
render_border_label(
- label,
+ Text.from_markup(label),
True,
"round",
999,
@@ -210,7 +222,7 @@ def test_render_border_label():
# Implicit test on the number of segments returned:
blank1, what, is_up, with_you, blank2 = render_border_label(
- label,
+ Text.from_markup(label),
True,
"round",
9999,
@@ -239,7 +251,7 @@ def test_render_border_label():
assert with_you == expected_with_you
blank1, what, blank2 = render_border_label(
- label,
+ Text.from_markup(label),
True,
"round",
5 + 4, # 5 where "What…" fits + 2 for the blank spaces + 2 for the corners.