import pytest from textual._node_list import DuplicateIds from textual.app import App, ComposeResult from textual.css.errors import StyleValueError from textual.css.query import NoMatches from textual.geometry import Size from textual.widget import MountError, Widget from textual.widgets import Label @pytest.mark.parametrize( "set_val, get_val, style_str", [ [True, True, "visible"], [False, False, "hidden"], ["hidden", False, "hidden"], ["visible", True, "visible"], ], ) def test_widget_set_visible_true(set_val, get_val, style_str): widget = Widget() widget.visible = set_val assert widget.visible is get_val assert widget.styles.visibility == style_str def test_widget_set_visible_invalid_string(): widget = Widget() with pytest.raises(StyleValueError): widget.visible = "nope! no widget for me!" assert widget.visible def test_widget_content_width(): class TextWidget(Widget): def __init__(self, text: str, id: str) -> None: self.text = text super().__init__(id=id) self.expand = False self.shrink = True def render(self) -> str: return self.text widget1 = TextWidget("foo", id="widget1") widget2 = TextWidget("foo\nbar", id="widget2") widget3 = TextWidget("foo\nbar\nbaz", id="widget3") app = App() app._set_active() width = widget1.get_content_width(Size(20, 20), Size(80, 24)) height = widget1.get_content_height(Size(20, 20), Size(80, 24), width) assert width == 3 assert height == 1 width = widget2.get_content_width(Size(20, 20), Size(80, 24)) height = widget2.get_content_height(Size(20, 20), Size(80, 24), width) assert width == 3 assert height == 2 width = widget3.get_content_width(Size(20, 20), Size(80, 24)) height = widget3.get_content_height(Size(20, 20), Size(80, 24), width) assert width == 3 assert height == 3 class GetByIdApp(App): def compose(self) -> ComposeResult: grandchild1 = Widget(id="grandchild1") child1 = Widget(grandchild1, id="child1") child2 = Widget(id="child2") yield Widget( child1, child2, id="parent", ) @pytest.fixture async def hierarchy_app(): app = GetByIdApp() async with app.run_test(): yield app @pytest.fixture async def parent(hierarchy_app): yield hierarchy_app.get_widget_by_id("parent") def test_get_child_by_id_gets_first_child(parent): child = parent.get_child_by_id(id="child1") assert child.id == "child1" assert child.get_child_by_id(id="grandchild1").id == "grandchild1" assert parent.get_child_by_id(id="child2").id == "child2" def test_get_child_by_id_no_matching_child(parent): with pytest.raises(NoMatches): parent.get_child_by_id(id="doesnt-exist") def test_get_child_by_id_only_immediate_descendents(parent): with pytest.raises(NoMatches): parent.get_child_by_id(id="grandchild1") def test_get_widget_by_id_no_matching_child(parent): with pytest.raises(NoMatches): parent.get_widget_by_id(id="i-dont-exist") def test_get_widget_by_id_non_immediate_descendants(parent): result = parent.get_widget_by_id("grandchild1") assert result.id == "grandchild1" def test_get_widget_by_id_immediate_descendants(parent): result = parent.get_widget_by_id("child1") assert result.id == "child1" def test_get_widget_by_id_doesnt_return_self(parent): with pytest.raises(NoMatches): parent.get_widget_by_id("parent") def test_get_widgets_app_delegated(hierarchy_app, parent): # Check that get_child_by_id finds the parent, which is a child of the default Screen queried_parent = hierarchy_app.get_child_by_id("parent") assert queried_parent is parent # Check that the grandchild (descendant of the default screen) is found grandchild = hierarchy_app.get_widget_by_id("grandchild1") assert grandchild.id == "grandchild1" def test_widget_mount_ids_must_be_unique_mounting_all_in_one_go(parent): widget1 = Widget(id="hello") widget2 = Widget(id="hello") with pytest.raises(MountError): parent.mount(widget1, widget2) def test_widget_mount_ids_must_be_unique_mounting_multiple_calls(parent): widget1 = Widget(id="hello") widget2 = Widget(id="hello") parent.mount(widget1) with pytest.raises(DuplicateIds): parent.mount(widget2) # Regression test for https://github.com/Textualize/textual/issues/1634 async def test_remove(): class RemoveMeLabel(Label): async def on_mount(self) -> None: await self.run_action("app.remove_all") class Container(Widget): async def clear(self) -> None: await self.query("*").remove() class RemoveApp(App): def compose(self) -> ComposeResult: yield Container(RemoveMeLabel()) async def action_remove_all(self) -> None: await self.query_one(Container).clear() self.exit(123) app = RemoveApp() async with app.run_test() as pilot: await pilot.press("r") await pilot.pause() assert app.return_value == 123