mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
@@ -277,5 +277,5 @@ async def test_scrollbar_gutter(
|
||||
app = MyTestApp(test_name="scrollbar_gutter", size=Size(80, 10))
|
||||
await app.boot_and_shutdown()
|
||||
|
||||
assert text_widget.size.width == expected_text_widget_width
|
||||
assert text_widget.outer_size.width == expected_text_widget_width
|
||||
assert container.scrollbars_enabled[0] is expects_vertical_scrollbar
|
||||
|
||||
25
tests/test_border.py
Normal file
25
tests/test_border.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from textual._border import get_box, render_row
|
||||
|
||||
|
||||
def test_border_render_row():
|
||||
|
||||
style = Style.parse("red")
|
||||
row = (Segment("┏", style), Segment("━", style), Segment("┓", style))
|
||||
|
||||
assert render_row(row, 5, False, False) == [Segment(row[1].text * 5, row[1].style)]
|
||||
assert render_row(row, 5, True, False) == [
|
||||
row[0],
|
||||
Segment(row[1].text * 4, row[1].style),
|
||||
]
|
||||
assert render_row(row, 5, False, True) == [
|
||||
Segment(row[1].text * 4, row[1].style),
|
||||
row[2],
|
||||
]
|
||||
assert render_row(row, 5, True, True) == [
|
||||
row[0],
|
||||
Segment(row[1].text * 3, row[1].style),
|
||||
row[2],
|
||||
]
|
||||
@@ -279,11 +279,11 @@ def test_size_sub():
|
||||
|
||||
|
||||
def test_region_x_extents():
|
||||
assert Region(5, 10, 20, 30).x_extents == (5, 25)
|
||||
assert Region(5, 10, 20, 30).column_span == (5, 25)
|
||||
|
||||
|
||||
def test_region_y_extents():
|
||||
assert Region(5, 10, 20, 30).y_extents == (10, 40)
|
||||
assert Region(5, 10, 20, 30).line_span == (10, 40)
|
||||
|
||||
|
||||
def test_region_x_max():
|
||||
@@ -294,12 +294,12 @@ def test_region_y_max():
|
||||
assert Region(5, 10, 20, 30).bottom == 40
|
||||
|
||||
|
||||
def test_region_x_range():
|
||||
assert Region(5, 10, 20, 30).x_range == range(5, 25)
|
||||
def test_region_columns_range():
|
||||
assert Region(5, 10, 20, 30).column_range == range(5, 25)
|
||||
|
||||
|
||||
def test_region_y_range():
|
||||
assert Region(5, 10, 20, 30).y_range == range(10, 40)
|
||||
def test_region_lines_range():
|
||||
assert Region(5, 10, 20, 30).line_range == range(10, 40)
|
||||
|
||||
|
||||
def test_region_reset_offset():
|
||||
|
||||
@@ -144,7 +144,7 @@ async def test_composition_of_vertical_container_with_children(
|
||||
async with app.in_running_state():
|
||||
# root widget checks:
|
||||
root_widget = cast(Widget, app.get_child("root"))
|
||||
assert root_widget.size == expected_screen_size
|
||||
assert root_widget.outer_size == expected_screen_size
|
||||
root_widget_region = app.screen.find_widget(root_widget).region
|
||||
assert root_widget_region == (
|
||||
0,
|
||||
@@ -158,7 +158,7 @@ async def test_composition_of_vertical_container_with_children(
|
||||
|
||||
# placeholder widgets checks:
|
||||
for placeholder in app_placeholders:
|
||||
assert placeholder.size == expected_placeholders_size
|
||||
assert placeholder.outer_size == expected_placeholders_size
|
||||
assert placeholder.styles.offset.x.value == 0.0
|
||||
assert app.screen.get_offset(placeholder).x == expected_placeholders_offset_x
|
||||
|
||||
@@ -224,7 +224,7 @@ async def test_border_edge_types_impact_on_widget_size(
|
||||
)
|
||||
assert box_inner_size == expected_box_inner_size
|
||||
|
||||
assert border_target.size == expected_box_size
|
||||
assert border_target.outer_size == expected_box_size
|
||||
|
||||
top_left_edge_style = app.screen.get_style_at(0, 0)
|
||||
top_left_edge_color = top_left_edge_style.color.name
|
||||
|
||||
@@ -2,7 +2,7 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
|
||||
from textual._segment_tools import line_crop
|
||||
from textual._segment_tools import line_crop, line_trim, line_pad
|
||||
|
||||
|
||||
def test_line_crop():
|
||||
@@ -62,3 +62,52 @@ def test_line_crop_edge_2():
|
||||
expected = []
|
||||
print(repr(result))
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_line_trim():
|
||||
segments = [Segment("foo")]
|
||||
|
||||
assert line_trim(segments, False, False) == segments
|
||||
assert line_trim(segments, True, False) == [Segment("oo")]
|
||||
assert line_trim(segments, False, True) == [Segment("fo")]
|
||||
assert line_trim(segments, True, True) == [Segment("o")]
|
||||
|
||||
fob_segments = [Segment("f"), Segment("o"), Segment("b")]
|
||||
|
||||
assert line_trim(fob_segments, True, False) == [
|
||||
Segment("o"),
|
||||
Segment("b"),
|
||||
]
|
||||
|
||||
assert line_trim(fob_segments, False, True) == [
|
||||
Segment("f"),
|
||||
Segment("o"),
|
||||
]
|
||||
|
||||
assert line_trim(fob_segments, True, True) == [
|
||||
Segment("o"),
|
||||
]
|
||||
|
||||
assert line_trim([], True, True) == []
|
||||
|
||||
|
||||
def test_line_pad():
|
||||
segments = [Segment("foo"), Segment("bar")]
|
||||
style = Style.parse("red")
|
||||
assert line_pad(segments, 2, 3, style) == [
|
||||
Segment(" ", style),
|
||||
*segments,
|
||||
Segment(" ", style),
|
||||
]
|
||||
|
||||
assert line_pad(segments, 0, 3, style) == [
|
||||
*segments,
|
||||
Segment(" ", style),
|
||||
]
|
||||
|
||||
assert line_pad(segments, 2, 0, style) == [
|
||||
Segment(" ", style),
|
||||
*segments,
|
||||
]
|
||||
|
||||
assert line_pad(segments, 0, 0, style) == segments
|
||||
|
||||
270
tests/test_styles_cache.py
Normal file
270
tests/test_styles_cache.py
Normal file
@@ -0,0 +1,270 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.segment import Segment
|
||||
|
||||
from textual.color import Color
|
||||
from textual.geometry import Region, Size
|
||||
from textual.css.styles import Styles
|
||||
from textual._styles_cache import StylesCache
|
||||
from textual._types import Lines
|
||||
|
||||
|
||||
def _extract_content(lines: Lines):
|
||||
"""Extract the text content from lines."""
|
||||
content = ["".join(segment.text for segment in line) for line in lines]
|
||||
return content
|
||||
|
||||
|
||||
def test_set_dirty():
|
||||
cache = StylesCache()
|
||||
cache.set_dirty(Region(3, 4, 10, 2))
|
||||
assert not cache.is_dirty(3)
|
||||
assert cache.is_dirty(4)
|
||||
assert cache.is_dirty(5)
|
||||
assert not cache.is_dirty(6)
|
||||
|
||||
|
||||
def test_no_styles():
|
||||
"""Test that empty style returns the content un-altered"""
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
styles = Styles()
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(3, 3),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
content.__getitem__,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
expected = [
|
||||
[Segment("foo", styles.rich_style)],
|
||||
[Segment("bar", styles.rich_style)],
|
||||
[Segment("baz", styles.rich_style)],
|
||||
]
|
||||
assert lines == expected
|
||||
|
||||
|
||||
def test_border():
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
styles = Styles()
|
||||
styles.border = ("heavy", "white")
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(5, 5),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
content.__getitem__,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
|
||||
text_content = _extract_content(lines)
|
||||
|
||||
expected_text = [
|
||||
"┏━━━┓",
|
||||
"┃foo┃",
|
||||
"┃bar┃",
|
||||
"┃baz┃",
|
||||
"┗━━━┛",
|
||||
]
|
||||
|
||||
assert text_content == expected_text
|
||||
|
||||
|
||||
def test_padding():
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
styles = Styles()
|
||||
styles.padding = 1
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(5, 5),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
content.__getitem__,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
|
||||
text_content = _extract_content(lines)
|
||||
|
||||
expected_text = [
|
||||
" ",
|
||||
" foo ",
|
||||
" bar ",
|
||||
" baz ",
|
||||
" ",
|
||||
]
|
||||
|
||||
assert text_content == expected_text
|
||||
|
||||
|
||||
def test_padding_border():
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
styles = Styles()
|
||||
styles.padding = 1
|
||||
styles.border = ("heavy", "white")
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(7, 7),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
content.__getitem__,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
|
||||
text_content = _extract_content(lines)
|
||||
|
||||
expected_text = [
|
||||
"┏━━━━━┓",
|
||||
"┃ ┃",
|
||||
"┃ foo ┃",
|
||||
"┃ bar ┃",
|
||||
"┃ baz ┃",
|
||||
"┃ ┃",
|
||||
"┗━━━━━┛",
|
||||
]
|
||||
|
||||
assert text_content == expected_text
|
||||
|
||||
|
||||
def test_outline():
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
styles = Styles()
|
||||
styles.outline = ("heavy", "white")
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(3, 3),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
content.__getitem__,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
|
||||
text_content = _extract_content(lines)
|
||||
expected_text = [
|
||||
"┏━┓",
|
||||
"┃a┃",
|
||||
"┗━┛",
|
||||
]
|
||||
assert text_content == expected_text
|
||||
|
||||
|
||||
def test_crop():
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
styles = Styles()
|
||||
styles.padding = 1
|
||||
styles.border = ("heavy", "white")
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(7, 7),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
content.__getitem__,
|
||||
content_size=Size(3, 3),
|
||||
crop=Region(2, 2, 3, 3),
|
||||
)
|
||||
text_content = _extract_content(lines)
|
||||
expected_text = [
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
]
|
||||
assert text_content == expected_text
|
||||
|
||||
|
||||
def test_dirty_cache():
|
||||
"""Check that we only render content once or if it has been marked as dirty."""
|
||||
|
||||
content = [
|
||||
[Segment("foo")],
|
||||
[Segment("bar")],
|
||||
[Segment("baz")],
|
||||
]
|
||||
rendered_lines: list[int] = []
|
||||
|
||||
def get_content_line(y: int) -> list[Segment]:
|
||||
rendered_lines.append(y)
|
||||
return content[y]
|
||||
|
||||
styles = Styles()
|
||||
styles.padding = 1
|
||||
styles.border = ("heavy", "white")
|
||||
cache = StylesCache()
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(7, 7),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
get_content_line,
|
||||
)
|
||||
assert rendered_lines == [0, 1, 2]
|
||||
del rendered_lines[:]
|
||||
|
||||
text_content = _extract_content(lines)
|
||||
expected_text = [
|
||||
"┏━━━━━┓",
|
||||
"┃ ┃",
|
||||
"┃ foo ┃",
|
||||
"┃ bar ┃",
|
||||
"┃ baz ┃",
|
||||
"┃ ┃",
|
||||
"┗━━━━━┛",
|
||||
]
|
||||
assert text_content == expected_text
|
||||
|
||||
# Re-render styles, check that content was not requested
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(7, 7),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
get_content_line,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
assert rendered_lines == []
|
||||
del rendered_lines[:]
|
||||
text_content = _extract_content(lines)
|
||||
assert text_content == expected_text
|
||||
|
||||
# Mark 2 lines as dirty
|
||||
cache.set_dirty(Region(0, 2, 7, 2))
|
||||
|
||||
lines = cache.render(
|
||||
styles,
|
||||
Size(7, 7),
|
||||
Color.parse("blue"),
|
||||
Color.parse("green"),
|
||||
get_content_line,
|
||||
content_size=Size(3, 3),
|
||||
)
|
||||
assert rendered_lines == [0, 1]
|
||||
text_content = _extract_content(lines)
|
||||
assert text_content == expected_text
|
||||
@@ -159,7 +159,7 @@ class AppTest(App):
|
||||
|
||||
# We artificially tell the Compositor that the whole area should be refreshed
|
||||
screen._compositor._dirty_regions = {
|
||||
Region(0, 0, screen.size.width, screen.size.height),
|
||||
Region(0, 0, screen.outer_size.width, screen.outer_size.height),
|
||||
}
|
||||
screen.refresh(repaint=repaint, layout=layout)
|
||||
# We also have to make sure we have at least one dirty widget, or `screen._on_update()` will early return:
|
||||
|
||||
Reference in New Issue
Block a user