mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #1095 from davep/mount-relative
Add support for mounting widgets relative to other widgets
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from textual.widget import Widget
|
||||
from textual._node_list import NodeList
|
||||
|
||||
@@ -19,6 +21,16 @@ def test_repeat_add_one():
|
||||
nodes._append(widget)
|
||||
assert len(nodes)==1
|
||||
|
||||
def test_insert():
|
||||
nodes = NodeList()
|
||||
widget1 = Widget()
|
||||
widget2 = Widget()
|
||||
widget3 = Widget()
|
||||
nodes._append(widget1)
|
||||
nodes._append(widget3)
|
||||
nodes._insert(1,widget2)
|
||||
assert list(nodes) == [widget1,widget2,widget3]
|
||||
|
||||
def test_truthy():
|
||||
"""Does a node list act as a truthy object?"""
|
||||
nodes = NodeList()
|
||||
@@ -35,6 +47,15 @@ def test_contains():
|
||||
assert widget in nodes
|
||||
assert Widget() not in nodes
|
||||
|
||||
def test_index():
|
||||
"""Can we get the index of a widget in the list?"""
|
||||
widget = Widget()
|
||||
nodes = NodeList()
|
||||
with pytest.raises(ValueError):
|
||||
_ = nodes.index(widget)
|
||||
nodes._append(widget)
|
||||
assert nodes.index(widget) == 0
|
||||
|
||||
def test_remove():
|
||||
"""Can we remove a widget we've added?"""
|
||||
widget = Widget()
|
||||
|
||||
40
tests/test_widget_mount_point.py
Normal file
40
tests/test_widget_mount_point.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import pytest
|
||||
|
||||
from textual.widget import Widget
|
||||
|
||||
|
||||
class Content(Widget):
|
||||
pass
|
||||
|
||||
|
||||
class Body(Widget):
|
||||
pass
|
||||
|
||||
|
||||
def test_find_dom_spot():
|
||||
|
||||
# Build up a "fake" DOM for an application.
|
||||
screen = Widget(name="Screen")
|
||||
header = Widget(name="Header", id="header")
|
||||
body = Body(id="body")
|
||||
content = [Content(id=f"item{n}") for n in range(1000)]
|
||||
body._add_children(*content)
|
||||
footer = Widget(name="Footer", id="footer")
|
||||
screen._add_children(header, body, footer)
|
||||
|
||||
# Just as a quick double-check, make sure the main components are in
|
||||
# their intended place.
|
||||
assert list(screen.children) == [header, body, footer]
|
||||
|
||||
# Now check that we find what we're looking for in the places we expect
|
||||
# to find them.
|
||||
assert screen._find_mount_point(1) == (screen, 1)
|
||||
assert screen._find_mount_point(body) == screen._find_mount_point(1)
|
||||
assert screen._find_mount_point("Body") == screen._find_mount_point(body)
|
||||
assert screen._find_mount_point("#body") == screen._find_mount_point(1)
|
||||
|
||||
# Finally, let's be sure that we get an error if, for some odd reason,
|
||||
# we go looking for a widget that isn't actually part of the DOM we're
|
||||
# looking in.
|
||||
with pytest.raises(Widget.MountError):
|
||||
_ = screen._find_mount_point(Widget())
|
||||
113
tests/test_widget_mounting.py
Normal file
113
tests/test_widget_mounting.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import pytest
|
||||
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
|
||||
async def test_mount_via_app() -> None:
|
||||
"""Perform mount tests via the app."""
|
||||
|
||||
# Make a background set of widgets.
|
||||
widgets = [Static(id=f"starter-{n}") for n in range( 10 )]
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount the first one and make sure it's there.
|
||||
await pilot.app.mount(widgets[0])
|
||||
assert len(pilot.app.screen.children) == 1
|
||||
assert pilot.app.screen.children[0] == widgets[0]
|
||||
|
||||
# Mount the next 2 widgets via mount.
|
||||
await pilot.app.mount(*widgets[1:3])
|
||||
assert list(pilot.app.screen.children) == widgets[0:3]
|
||||
|
||||
# Finally mount the rest of the widgets via mount_all.
|
||||
await pilot.app.mount_all(widgets[3:])
|
||||
assert list(pilot.app.screen.children) == widgets
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget before -1, which is "before the end".
|
||||
penultimate = Static(id="penultimate")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(penultimate, before=-1)
|
||||
assert pilot.app.screen.children[-2] == penultimate
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget after -1, which is "at the end".
|
||||
ultimate = Static(id="ultimate")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(ultimate, after=-1)
|
||||
assert pilot.app.screen.children[-1] == ultimate
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget before -2, which is "before the penultimate".
|
||||
penpenultimate = Static(id="penpenultimate")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(penpenultimate, before=-2)
|
||||
assert pilot.app.screen.children[-3] == penpenultimate
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget after -2, which is "before the end".
|
||||
penultimate = Static(id="penultimate")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(penultimate, after=-2)
|
||||
assert pilot.app.screen.children[-2] == penultimate
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget before 0, which is "at the start".
|
||||
start = Static(id="start")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(start, before=0)
|
||||
assert pilot.app.screen.children[0] == start
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget after 0. You get the idea...
|
||||
second = Static(id="second")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(second, after=0)
|
||||
assert pilot.app.screen.children[1] == second
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget relative to another via query.
|
||||
queue_jumper = Static(id="queue-jumper")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(queue_jumper, after="#starter-5")
|
||||
assert pilot.app.screen.children[6] == queue_jumper
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Mount a widget relative to another via query.
|
||||
queue_jumper = Static(id="queue-jumper")
|
||||
await pilot.app.mount_all(widgets)
|
||||
await pilot.app.mount(queue_jumper, after=widgets[5])
|
||||
assert pilot.app.screen.children[6] == queue_jumper
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Make sure we get told off for trying to before and after.
|
||||
await pilot.app.mount_all(widgets)
|
||||
with pytest.raises(Static.MountError):
|
||||
await pilot.app.mount(Static(), before=2, after=2)
|
||||
|
||||
async with App().run_test() as pilot:
|
||||
# Make sure we get told off trying to mount relative to something
|
||||
# that isn't actually in the DOM.
|
||||
await pilot.app.mount_all(widgets)
|
||||
with pytest.raises(Static.MountError):
|
||||
await pilot.app.mount(Static(), before=Static())
|
||||
with pytest.raises(Static.MountError):
|
||||
await pilot.app.mount(Static(), after=Static())
|
||||
|
||||
# TODO: At the moment query_one() simply takes a query and returns the
|
||||
# .first() item. As such doing a query_one() that gets more than one
|
||||
# thing isn't an error, it just skims off the first thing. OTOH the
|
||||
# intention of before= and after= with a selector is that an exception
|
||||
# will be thrown -- the exception being the own that should be thrown
|
||||
# from query_one(). So, this test here is a TODO test because we'll wait
|
||||
# for a change to query_one() and then its exception will just bubble
|
||||
# up.
|
||||
#
|
||||
# See https://github.com/Textualize/textual/issues/1096
|
||||
#
|
||||
# async with App().run_test() as pilot:
|
||||
# # Make sure we get an error if we try and mount with a selector that
|
||||
# # results in more than one hit.
|
||||
# await pilot.app.mount_all(widgets)
|
||||
# with pytest.raises( ?Something? ):
|
||||
# await pilot.app.mount(Static(), before="Static")
|
||||
Reference in New Issue
Block a user