mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Remove draw.py, fix imports
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DrawStyle(Enum):
|
||||
NONE = "none"
|
||||
ASCII = "ascii"
|
||||
SQUARE = "square"
|
||||
HEAVY = "heavy"
|
||||
ROUNDED = "rounded"
|
||||
DOUBLE = "double"
|
||||
@@ -1,9 +1,7 @@
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from textual.devtools.server import _make_devtools_aiohttp_app
|
||||
from textual.devtools.client import DevtoolsClient
|
||||
from textual.devtools.server import _make_devtools_aiohttp_app
|
||||
from textual.devtools.service import DevtoolsService
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from datetime import datetime
|
||||
|
||||
import msgpack
|
||||
import pytest
|
||||
import time_machine
|
||||
from rich.align import Align
|
||||
from rich.console import Console
|
||||
from rich.segment import Segment
|
||||
|
||||
import msgpack
|
||||
from tests.utilities.render import wait_for_predicate
|
||||
from textual.devtools.renderables import DevConsoleLog, DevConsoleNotice
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import types
|
||||
from asyncio import Queue
|
||||
from datetime import datetime
|
||||
|
||||
import msgpack
|
||||
import time_machine
|
||||
from aiohttp.web_ws import WebSocketResponse
|
||||
from rich.console import ConsoleDimensions
|
||||
from rich.panel import Panel
|
||||
import msgpack
|
||||
|
||||
from tests.utilities.render import wait_for_predicate
|
||||
from textual.devtools.client import DevtoolsClient
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import json
|
||||
from contextlib import redirect_stdout
|
||||
from datetime import datetime
|
||||
|
||||
import msgpack
|
||||
import time_machine
|
||||
|
||||
import msgpack
|
||||
from textual.devtools.redirect_output import StdoutRedirector
|
||||
|
||||
TIMESTAMP = 1649166819
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from textual.layouts.vertical import VerticalLayout
|
||||
from textual.layouts.factory import get_layout, MissingLayout
|
||||
from textual.layouts.vertical import VerticalLayout
|
||||
|
||||
|
||||
def test_get_layout_valid_layout():
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from textual._border import get_box, render_row
|
||||
from textual._border import render_row
|
||||
|
||||
|
||||
def test_border_render_row():
|
||||
|
||||
@@ -8,7 +8,6 @@ import pytest
|
||||
|
||||
from textual._easing import EASING
|
||||
|
||||
|
||||
POINTS = [
|
||||
0.0,
|
||||
0.05,
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
from __future__ import annotations
|
||||
from typing import cast, List, Sequence
|
||||
|
||||
import pytest
|
||||
from rich.console import RenderableType
|
||||
from rich.text import Text
|
||||
|
||||
from tests.utilities.test_app import AppTest
|
||||
from textual.app import ComposeResult
|
||||
from textual.css.types import EdgeType
|
||||
from textual.geometry import Size
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
pytestmark = pytest.mark.integration_test
|
||||
|
||||
# Let's allow ourselves some abbreviated names for those tests,
|
||||
# in order to make the test cases a bit easier to read :-)
|
||||
SCREEN_W = 100 # width of our Screens
|
||||
SCREEN_H = 8 # height of our Screens
|
||||
SCREEN_SIZE = Size(SCREEN_W, SCREEN_H)
|
||||
PLACEHOLDERS_DEFAULT_H = 3 # the default height for our Placeholder widgets
|
||||
|
||||
# As per Widget's CSS property, by default Widgets have a horizontal scrollbar of size 1
|
||||
# and a vertical scrollbar of size 2:
|
||||
SCROLL_H_SIZE = 1
|
||||
SCROLL_V_SIZE = 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"placeholders_count",
|
||||
"root_container_style",
|
||||
"placeholders_style",
|
||||
"expected_root_widget_virtual_size",
|
||||
"expected_placeholders_size",
|
||||
"expected_placeholders_offset_x",
|
||||
),
|
||||
(
|
||||
*[
|
||||
[
|
||||
1,
|
||||
f"border: {invisible_border_edge};", # #root has no visible border
|
||||
"", # no specific placeholder style
|
||||
# #root's virtual size=screen size
|
||||
(SCREEN_W, SCREEN_H),
|
||||
# placeholders width=same than screen :: height=default height
|
||||
(SCREEN_W, PLACEHOLDERS_DEFAULT_H),
|
||||
# placeholders should be at offset 0
|
||||
0,
|
||||
]
|
||||
for invisible_border_edge in ("", "none", "hidden")
|
||||
],
|
||||
[
|
||||
1,
|
||||
"border: solid white;", # #root has a visible border
|
||||
"", # no specific placeholder style
|
||||
# #root's virtual size is smaller because of its borders
|
||||
(SCREEN_W - 2, SCREEN_H - 2),
|
||||
# placeholders width=same than screen, minus 2 borders :: height=default height minus 2 borders
|
||||
(SCREEN_W - 2, PLACEHOLDERS_DEFAULT_H),
|
||||
# placeholders should be at offset 1 because of #root's border
|
||||
1,
|
||||
],
|
||||
[
|
||||
4,
|
||||
"border: solid white;", # #root has a visible border
|
||||
"", # no specific placeholder style
|
||||
# #root's virtual height should be as high as its stacked content
|
||||
(SCREEN_W - 2 - 1, PLACEHOLDERS_DEFAULT_H * 4),
|
||||
# placeholders width=same than screen, minus 2 borders, minus scrollbar :: height=default height minus 2 borders
|
||||
(SCREEN_W - 2 - SCROLL_V_SIZE, PLACEHOLDERS_DEFAULT_H),
|
||||
# placeholders should be at offset 1 because of #root's border
|
||||
1,
|
||||
],
|
||||
[
|
||||
1,
|
||||
"border: solid white;", # #root has a visible border
|
||||
"align: center top;", # placeholders are centered horizontally
|
||||
# #root's virtual size=screen size
|
||||
(SCREEN_W, SCREEN_H),
|
||||
# placeholders width=same than screen, minus 2 borders :: height=default height
|
||||
(SCREEN_W - 2, PLACEHOLDERS_DEFAULT_H),
|
||||
# placeholders should be at offset 1 because of #root's border
|
||||
1,
|
||||
],
|
||||
[
|
||||
4,
|
||||
"border: solid white;", # #root has a visible border
|
||||
"align: center top;", # placeholders are centered horizontally
|
||||
# #root's virtual height should be as high as its stacked content
|
||||
(
|
||||
SCREEN_W - 2 - SCROLL_V_SIZE,
|
||||
PLACEHOLDERS_DEFAULT_H * 4,
|
||||
),
|
||||
# placeholders width=same than screen, minus 2 borders, minus scrollbar :: height=default height
|
||||
(SCREEN_W - 2 - SCROLL_V_SIZE, PLACEHOLDERS_DEFAULT_H),
|
||||
# placeholders should be at offset 1 because of #root's border
|
||||
1,
|
||||
],
|
||||
),
|
||||
)
|
||||
async def test_composition_of_vertical_container_with_children(
|
||||
placeholders_count: int,
|
||||
root_container_style: str,
|
||||
placeholders_style: str,
|
||||
expected_placeholders_size: tuple[int, int],
|
||||
expected_root_widget_virtual_size: tuple[int, int],
|
||||
expected_placeholders_offset_x: int,
|
||||
):
|
||||
class VerticalContainer(Widget):
|
||||
DEFAULT_CSS = (
|
||||
"""
|
||||
VerticalContainer {
|
||||
layout: vertical;
|
||||
overflow: hidden auto;
|
||||
${root_container_style}
|
||||
}
|
||||
|
||||
VerticalContainer Placeholder {
|
||||
height: ${placeholders_height};
|
||||
${placeholders_style}
|
||||
}
|
||||
""".replace(
|
||||
"${root_container_style}", root_container_style
|
||||
)
|
||||
.replace("${placeholders_height}", str(PLACEHOLDERS_DEFAULT_H))
|
||||
.replace("${placeholders_style}", placeholders_style)
|
||||
)
|
||||
|
||||
class MyTestApp(AppTest):
|
||||
def compose(self) -> ComposeResult:
|
||||
placeholders = [
|
||||
Placeholder(id=f"placeholder_{i}", name=f"Placeholder #{i}")
|
||||
for i in range(placeholders_count)
|
||||
]
|
||||
|
||||
yield VerticalContainer(*placeholders, id="root")
|
||||
|
||||
app = MyTestApp(size=SCREEN_SIZE, test_name="compositor")
|
||||
|
||||
expected_screen_size = SCREEN_SIZE
|
||||
|
||||
async with app.in_running_state():
|
||||
# root widget checks:
|
||||
root_widget = cast(Widget, app.get_child("root"))
|
||||
assert root_widget.outer_size == expected_screen_size
|
||||
root_widget_region = app.screen.find_widget(root_widget).region
|
||||
assert root_widget_region == (
|
||||
0,
|
||||
0,
|
||||
expected_screen_size.width,
|
||||
expected_screen_size.height,
|
||||
)
|
||||
|
||||
app_placeholders = cast(List[Widget], app.query("Placeholder"))
|
||||
assert len(app_placeholders) == placeholders_count
|
||||
|
||||
# placeholder widgets checks:
|
||||
for placeholder in app_placeholders:
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"edge_type,expected_box_inner_size,expected_box_size,expected_top_left_edge_color,expects_visible_char_at_top_left_edge",
|
||||
(
|
||||
# These first 3 types of border edge types are synonyms, and display no borders:
|
||||
["", Size(SCREEN_W, 1), Size(SCREEN_W, 1), "black", False],
|
||||
["none", Size(SCREEN_W, 1), Size(SCREEN_W, 1), "black", False],
|
||||
["hidden", Size(SCREEN_W, 1), Size(SCREEN_W, 1), "black", False],
|
||||
# Let's transition to "blank": we still see no visible border, but the size is increased
|
||||
# as the gutter space is reserved the same way it would be with a border:
|
||||
["blank", Size(SCREEN_W - 2, 1), Size(SCREEN_W, 3), "#ffffff", False],
|
||||
# And now for the "normally visible" border edge types:
|
||||
# --> we see a visible border, and the size is increased:
|
||||
*[
|
||||
[edge_style, Size(SCREEN_W - 2, 1), Size(SCREEN_W, 3), "#ffffff", True]
|
||||
for edge_style in [
|
||||
"round",
|
||||
"solid",
|
||||
"double",
|
||||
"dashed",
|
||||
"heavy",
|
||||
"inner",
|
||||
"outer",
|
||||
"hkey",
|
||||
"vkey",
|
||||
]
|
||||
],
|
||||
),
|
||||
)
|
||||
async def test_border_edge_types_impact_on_widget_size(
|
||||
edge_type: EdgeType,
|
||||
expected_box_inner_size: Size,
|
||||
expected_box_size: Size,
|
||||
expected_top_left_edge_color: str,
|
||||
expects_visible_char_at_top_left_edge: bool,
|
||||
):
|
||||
class BorderTarget(Widget):
|
||||
def render(self) -> RenderableType:
|
||||
return Text("border target", style="black on yellow", justify="center")
|
||||
|
||||
border_target = BorderTarget()
|
||||
border_target.styles.height = "auto"
|
||||
border_target.styles.border = (edge_type, "white")
|
||||
|
||||
class MyTestApp(AppTest):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield border_target
|
||||
|
||||
app = MyTestApp(size=SCREEN_SIZE, test_name="border_edge_types")
|
||||
|
||||
await app.boot_and_shutdown()
|
||||
|
||||
box_inner_size = Size(
|
||||
border_target.content_region.width,
|
||||
border_target.content_region.height,
|
||||
)
|
||||
assert box_inner_size == expected_box_inner_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
|
||||
assert top_left_edge_color.upper() == expected_top_left_edge_color.upper()
|
||||
|
||||
top_left_edge_char = app.get_char_at(0, 0)
|
||||
top_left_edge_char_is_a_visible_one = top_left_edge_char != " "
|
||||
assert top_left_edge_char_is_a_visible_one == expects_visible_char_at_top_left_edge
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"large_widget_size,container_style,expected_large_widget_visible_region_size",
|
||||
(
|
||||
# In these tests we're going to insert a "large widget"
|
||||
# into a container with size (20,20).
|
||||
# ---------------- let's start!
|
||||
# no overflow/scrollbar instructions: no scrollbars
|
||||
[Size(30, 30), "color: red", Size(20, 20)],
|
||||
# explicit hiding of the overflow: no scrollbars either
|
||||
[Size(30, 30), "overflow: hidden", Size(20, 20)],
|
||||
# scrollbar for both directions
|
||||
[
|
||||
Size(30, 30),
|
||||
"overflow: auto",
|
||||
Size(
|
||||
20 - SCROLL_V_SIZE,
|
||||
20 - SCROLL_H_SIZE,
|
||||
),
|
||||
],
|
||||
# horizontal scrollbar
|
||||
[Size(30, 30), "overflow-x: auto", Size(20, 20 - SCROLL_H_SIZE)],
|
||||
# vertical scrollbar
|
||||
[Size(30, 30), "overflow-y: auto", Size(20 - SCROLL_V_SIZE, 20)],
|
||||
# scrollbar for both directions, custom scrollbar size
|
||||
[Size(30, 30), ("overflow: auto", "scrollbar-size: 3 5"), Size(20 - 5, 20 - 3)],
|
||||
# scrollbar for both directions, custom vertical scrollbar size
|
||||
[
|
||||
Size(30, 30),
|
||||
("overflow: auto", "scrollbar-size-vertical: 3"),
|
||||
Size(20 - 3, 20 - SCROLL_H_SIZE),
|
||||
],
|
||||
# scrollbar for both directions, custom horizontal scrollbar size
|
||||
[
|
||||
Size(30, 30),
|
||||
("overflow: auto", "scrollbar-size-horizontal: 3"),
|
||||
Size(20 - SCROLL_V_SIZE, 20 - 3),
|
||||
],
|
||||
# scrollbar needed only horizontally, custom scrollbar size
|
||||
[
|
||||
Size(30, 20),
|
||||
("overflow: auto", "scrollbar-size: 3 3"),
|
||||
Size(20, 20 - 3),
|
||||
],
|
||||
),
|
||||
)
|
||||
async def test_scrollbar_size_impact_on_the_layout(
|
||||
large_widget_size: Size,
|
||||
container_style: str | Sequence[str],
|
||||
expected_large_widget_visible_region_size: Size,
|
||||
):
|
||||
class LargeWidget(Widget):
|
||||
def on_mount(self):
|
||||
self.styles.width = large_widget_size[0]
|
||||
self.styles.height = large_widget_size[1]
|
||||
|
||||
container_style_rules = (
|
||||
[container_style] if isinstance(container_style, str) else container_style
|
||||
)
|
||||
|
||||
class LargeWidgetContainer(Widget):
|
||||
# TODO: Once textual#581 ("Default versus User CSS") is solved the following CSS should just use the
|
||||
# "LargeWidgetContainer" selector, without having to use a more specific one to be able to override Widget's CSS:
|
||||
DEFAULT_CSS = """
|
||||
#large-widget-container {
|
||||
width: 20;
|
||||
height: 20;
|
||||
${container_style};
|
||||
}
|
||||
""".replace(
|
||||
"${container_style}",
|
||||
";\n".join(container_style_rules),
|
||||
)
|
||||
|
||||
large_widget = LargeWidget()
|
||||
large_widget.expand = False
|
||||
container = LargeWidgetContainer(large_widget, id="large-widget-container")
|
||||
|
||||
class MyTestApp(AppTest):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield container
|
||||
|
||||
app = MyTestApp(size=Size(40, 40), test_name="scrollbar_size_impact_on_the_layout")
|
||||
|
||||
await app.boot_and_shutdown()
|
||||
|
||||
compositor = app.screen._compositor
|
||||
widgets_map = compositor.map
|
||||
large_widget_visible_region_size = widgets_map[large_widget].visible_region.size
|
||||
assert large_widget_visible_region_size == expected_large_widget_visible_region_size
|
||||
@@ -1,4 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence, cast
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from typing import NamedTuple
|
||||
|
||||
import pytest
|
||||
|
||||
from textual._layout_resolve import layout_resolve
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
|
||||
from textual.geometry import Size
|
||||
from textual.css.scalar import Scalar
|
||||
from textual._resolve import resolve
|
||||
from textual.css.scalar import Scalar
|
||||
from textual.geometry import Size
|
||||
|
||||
|
||||
def test_resolve_empty():
|
||||
|
||||
@@ -5,7 +5,6 @@ import pytest
|
||||
from textual.app import App, ScreenStackError
|
||||
from textual.screen import Screen
|
||||
|
||||
|
||||
skip_py310 = pytest.mark.skipif(
|
||||
sys.version_info.minor == 10 and sys.version_info.major == 3,
|
||||
reason="segfault on py3.10",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
|
||||
from textual._segment_tools import line_crop, line_trim, line_pad
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ from __future__ import annotations
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
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
|
||||
from textual.color import Color
|
||||
from textual.css.styles import Styles
|
||||
from textual.geometry import Region, Size
|
||||
|
||||
|
||||
def _extract_content(lines: Lines):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pytest
|
||||
from rich.style import Style
|
||||
|
||||
from textual.app import App
|
||||
from textual.css.errors import StyleValueError
|
||||
|
||||
@@ -14,12 +14,13 @@ from rich.console import Console
|
||||
from textual import events, errors
|
||||
from textual._ansi_sequences import SYNC_START
|
||||
from textual._clock import _Clock
|
||||
from textual.app import WINDOWS
|
||||
from textual._context import active_app
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.app import WINDOWS
|
||||
from textual.driver import Driver
|
||||
from textual.geometry import Size, Region
|
||||
|
||||
|
||||
# N.B. These classes would better be named TestApp/TestConsole/TestDriver/etc,
|
||||
# but it makes pytest emit warning as it will try to collect them as classes containing test cases :-/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user