mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
* Add Widget.border_title and border_subtitle. Related issues: #1864 * Test setting border_(sub)title. * Add border (sub)title references to StylesCache. These internal references will make it easier for the instance of 'StylesCache' to know which border (sub)title to use, if/when needed. * Add method to render border label. * Add styles to align border (sub)title. * Render border labels. * Update styles template. * Make new 'render_row' parameters optional. * Add (sub)title border snapshot tests. * Document border (sub)title and styles. * Pass (sub)title directly as arguments. Get rid of the watchers to make data flow easier to follow. Related comment: https://github.com/Textualize/textual/pull/2064/files\#r1137746697 * Tweak example. * Fix render_border_label. This was wrong because border labels can be composed of multiple segments if they contain multiple styles. Additionally, we want to render a single blank space of padding around the title. * Ensure we get no label when there's no space. * Add tests for border label rendering. * 'render_border_label' now returns iterable of segments. * Add label to render_row. * Fix calling signature in tests. * Add padding to snapshot tests. * Fix changelog. * Update snapshot tests. * Update snapshot tests. * Border labels expand if there's no corners. * Update CHANGELOG.md * Fix docs. * Remove irrelevant line. * Fix snapshot tests. * Don't share Console among tests. * Simplify example in styles guide. * Avoid expensive function call when possible. * rewording * positive branch first * remove wasteful indirection * fix changelog --------- Co-authored-by: Will McGugan <willmcgugan@gmail.com>
232 lines
6.4 KiB
Python
232 lines
6.4 KiB
Python
import pytest
|
|
from rich.console import Console
|
|
from rich.segment import Segment
|
|
from rich.style import Style
|
|
|
|
from textual._border import render_border_label, render_row
|
|
from textual.widget import Widget
|
|
|
|
_EMPTY_STYLE = Style()
|
|
_BLANK_SEGMENT = Segment(" ", _EMPTY_STYLE)
|
|
_WIDE_CONSOLE = Console(width=9999)
|
|
|
|
|
|
def test_border_render_row():
|
|
style = Style.parse("red")
|
|
row = (Segment("┏", style), Segment("━", style), Segment("┓", style))
|
|
|
|
assert list(render_row(row, 5, False, False, ())) == [
|
|
Segment(row[1].text * 5, row[1].style)
|
|
]
|
|
assert list(render_row(row, 5, True, False, ())) == [
|
|
row[0],
|
|
Segment(row[1].text * 4, row[1].style),
|
|
]
|
|
assert list(render_row(row, 5, False, True, ())) == [
|
|
Segment(row[1].text * 4, row[1].style),
|
|
row[2],
|
|
]
|
|
assert list(render_row(row, 5, True, True, ())) == [
|
|
row[0],
|
|
Segment(row[1].text * 3, row[1].style),
|
|
row[2],
|
|
]
|
|
|
|
|
|
def test_border_title_single_line():
|
|
"""The border_title gets set to a single line even when multiple lines are provided."""
|
|
widget = Widget()
|
|
|
|
widget.border_title = ""
|
|
assert widget.border_title == ""
|
|
|
|
widget.border_title = "How is life\ngoing for you?"
|
|
assert widget.border_title == "How is life"
|
|
|
|
widget.border_title = "How is life\n\rgoing for you?"
|
|
assert widget.border_title == "How is life"
|
|
|
|
widget.border_title = "Sorry you \r\n have to \n read this."
|
|
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 "
|
|
|
|
|
|
def test_border_subtitle_single_line():
|
|
"""The border_subtitle gets set to a single line even when multiple lines are provided."""
|
|
widget = Widget()
|
|
|
|
widget.border_subtitle = ""
|
|
assert widget.border_subtitle == ""
|
|
|
|
widget.border_subtitle = "How is life\ngoing for you?"
|
|
assert widget.border_subtitle == "How is life"
|
|
|
|
widget.border_subtitle = "How is life\n\rgoing for you?"
|
|
assert widget.border_subtitle == "How is life"
|
|
|
|
widget.border_subtitle = "Sorry you \r\n have to \n read this."
|
|
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 "
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
["width", "has_left_corner", "has_right_corner"],
|
|
[
|
|
(10, True, True),
|
|
(10, True, False),
|
|
(10, False, False),
|
|
(10, False, True),
|
|
(1, True, True),
|
|
(1, True, False),
|
|
(1, False, False),
|
|
(1, False, True),
|
|
],
|
|
)
|
|
def test_render_border_label_empty_label_skipped(
|
|
width: int, has_left_corner: bool, has_right_corner: bool
|
|
):
|
|
"""Test that we get an empty list of segments if there is no label to display."""
|
|
|
|
assert [] == list(
|
|
render_border_label(
|
|
"",
|
|
True,
|
|
"round",
|
|
width,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
_WIDE_CONSOLE,
|
|
has_left_corner,
|
|
has_right_corner,
|
|
)
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
["label", "width", "has_left_corner", "has_right_corner"],
|
|
[
|
|
("hey", 2, True, True),
|
|
("hey", 2, True, False),
|
|
("hey", 2, False, True),
|
|
("hey", 3, True, True),
|
|
("hey", 4, True, True),
|
|
],
|
|
)
|
|
def test_render_border_label_skipped_if_narrow(
|
|
label: str, width: int, has_left_corner: bool, has_right_corner: bool
|
|
):
|
|
"""Test that we skip rendering a label when we do not have space for it.
|
|
|
|
In order for us to have enough space for the label, we need to have space for the
|
|
corners that we need (none, just one, or both) and we need to be able to have two
|
|
blank spaces around the label (one on each side).
|
|
If we don't have space for all of these, we skip the label altogether.
|
|
"""
|
|
|
|
assert [] == list(
|
|
render_border_label(
|
|
label,
|
|
True,
|
|
"round",
|
|
width,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
_WIDE_CONSOLE,
|
|
has_left_corner,
|
|
has_right_corner,
|
|
)
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"label",
|
|
[
|
|
"Why did the scarecrow",
|
|
"win a Nobel prize?",
|
|
"because it was outstanding",
|
|
"in its field.",
|
|
],
|
|
)
|
|
def test_render_border_label_wide_plain(label: str):
|
|
"""Test label rendering in a wide area with no styling."""
|
|
|
|
BIG_NUM = 9999
|
|
args = (
|
|
True,
|
|
"round",
|
|
BIG_NUM,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
_WIDE_CONSOLE,
|
|
True,
|
|
True,
|
|
)
|
|
left, original_text, right = render_border_label(label, *args)
|
|
|
|
assert left == _BLANK_SEGMENT
|
|
assert right == _BLANK_SEGMENT
|
|
assert original_text == Segment(label, _EMPTY_STYLE)
|
|
|
|
|
|
def test_render_border_label():
|
|
"""Test label rendering with styling, with and without overflow."""
|
|
|
|
label = "[b][on red]What [i]is up[/on red] with you?[/]"
|
|
border_style = Style.parse("green on blue")
|
|
|
|
# Implicit test on the number of segments returned:
|
|
blank1, what, is_up, with_you, blank2 = render_border_label(
|
|
label,
|
|
True,
|
|
"round",
|
|
9999,
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
border_style,
|
|
_WIDE_CONSOLE,
|
|
True,
|
|
True,
|
|
)
|
|
|
|
expected_blank = Segment(" ", border_style)
|
|
assert blank1 == expected_blank
|
|
assert blank2 == expected_blank
|
|
|
|
what_style = Style.parse("b on red")
|
|
expected_what = Segment("What ", border_style + what_style)
|
|
assert what == expected_what
|
|
|
|
is_up_style = Style.parse("b on red i")
|
|
expected_is_up = Segment("is up", border_style + is_up_style)
|
|
assert is_up == expected_is_up
|
|
|
|
with_you_style = Style.parse("b i")
|
|
expected_with_you = Segment(" with you?", border_style + with_you_style)
|
|
assert with_you == expected_with_you
|
|
|
|
blank1, what, blank2 = render_border_label(
|
|
label,
|
|
True,
|
|
"round",
|
|
5 + 4, # 5 where "What…" fits + 2 for the blank spaces + 2 for the corners.
|
|
_EMPTY_STYLE,
|
|
_EMPTY_STYLE,
|
|
border_style,
|
|
_WIDE_CONSOLE,
|
|
True, # This corner costs 2 cells.
|
|
True, # This corner costs 2 cells.
|
|
)
|
|
|
|
assert blank1 == expected_blank
|
|
assert blank2 == expected_blank
|
|
|
|
expected_what = Segment("What…", border_style + what_style)
|
|
assert what == expected_what
|