mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
A new form of Checkbox will be arriving in Textual soon, working in conjunction with a RadioButton. What was called Checkbox is perhaps a wee bit heavyweight in terms of visual design, but is a style of widget that should remain. With this in mind we're renaming the current Checkbox to Switch. In all other respects its workings remains the same, only the name has changed. Things for people to watch out for: - Imports will need to be updated. - Queries will need to be updated; special attention will need to be paid to any queries that are string-based. - CSS will need to be changed if any Checkbox styling is happening, or if any Checkbox component styles are being used. See #1725 as the initial motivation and #1746 as the issue for this particular change.
204 lines
6.3 KiB
Python
204 lines
6.3 KiB
Python
import pytest
|
|
|
|
from textual.app import App
|
|
from textual.screen import Screen
|
|
from textual.widget import Widget
|
|
|
|
|
|
class Focusable(Widget, can_focus=True):
|
|
pass
|
|
|
|
|
|
class NonFocusable(Widget, can_focus=False, can_focus_children=False):
|
|
pass
|
|
|
|
|
|
class ChildrenFocusableOnly(Widget, can_focus=False, can_focus_children=True):
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def screen() -> Screen:
|
|
app = App()
|
|
app._set_active()
|
|
app.push_screen(Screen())
|
|
|
|
screen = app.screen
|
|
|
|
# The classes even/odd alternate along the focus chain.
|
|
# The classes in/out identify nested widgets.
|
|
screen._add_children(
|
|
Focusable(id="foo", classes="a"),
|
|
NonFocusable(id="bar"),
|
|
Focusable(Focusable(id="Paul", classes="c"), id="container1", classes="b"),
|
|
NonFocusable(Focusable(id="Jessica", classes="a"), id="container2"),
|
|
Focusable(id="baz", classes="b"),
|
|
ChildrenFocusableOnly(Focusable(id="child", classes="c")),
|
|
)
|
|
|
|
return screen
|
|
|
|
|
|
def test_focus_chain():
|
|
app = App()
|
|
app._set_active()
|
|
app.push_screen(Screen())
|
|
|
|
screen = app.screen
|
|
|
|
# Check empty focus chain
|
|
assert not screen.focus_chain
|
|
|
|
app.screen._add_children(
|
|
Focusable(id="foo"),
|
|
NonFocusable(id="bar"),
|
|
Focusable(Focusable(id="Paul"), id="container1"),
|
|
NonFocusable(Focusable(id="Jessica"), id="container2"),
|
|
Focusable(id="baz"),
|
|
ChildrenFocusableOnly(Focusable(id="child")),
|
|
)
|
|
|
|
focus_chain = [widget.id for widget in screen.focus_chain]
|
|
assert focus_chain == ["foo", "container1", "Paul", "baz", "child"]
|
|
|
|
|
|
def test_focus_next_and_previous(screen: Screen):
|
|
assert screen.focus_next().id == "foo"
|
|
assert screen.focus_next().id == "container1"
|
|
assert screen.focus_next().id == "Paul"
|
|
assert screen.focus_next().id == "baz"
|
|
assert screen.focus_next().id == "child"
|
|
|
|
assert screen.focus_previous().id == "baz"
|
|
assert screen.focus_previous().id == "Paul"
|
|
assert screen.focus_previous().id == "container1"
|
|
assert screen.focus_previous().id == "foo"
|
|
|
|
|
|
def test_focus_next_wrap_around(screen: Screen):
|
|
"""Ensure focusing the next widget wraps around the focus chain."""
|
|
screen.set_focus(screen.query_one("#child"))
|
|
assert screen.focused.id == "child"
|
|
|
|
assert screen.focus_next().id == "foo"
|
|
|
|
|
|
def test_focus_previous_wrap_around(screen: Screen):
|
|
"""Ensure focusing the previous widget wraps around the focus chain."""
|
|
screen.set_focus(screen.query_one("#foo"))
|
|
assert screen.focused.id == "foo"
|
|
|
|
assert screen.focus_previous().id == "child"
|
|
|
|
|
|
def test_wrap_around_selector(screen: Screen):
|
|
"""Ensure moving focus in both directions wraps around the focus chain."""
|
|
screen.set_focus(screen.query_one("#foo"))
|
|
assert screen.focused.id == "foo"
|
|
|
|
assert screen.focus_previous("#Paul").id == "Paul"
|
|
assert screen.focus_next("#foo").id == "foo"
|
|
|
|
|
|
def test_no_focus_empty_selector(screen: Screen):
|
|
"""Ensure focus is cleared when selector matches nothing."""
|
|
assert screen.focus_next("#bananas") is None
|
|
assert screen.focus_previous("#bananas") is None
|
|
|
|
screen.set_focus(screen.query_one("#foo"))
|
|
assert screen.focused is not None
|
|
assert screen.focus_next("bananas") is None
|
|
assert screen.focused is None
|
|
|
|
screen.set_focus(screen.query_one("#foo"))
|
|
assert screen.focused is not None
|
|
assert screen.focus_previous("bananas") is None
|
|
assert screen.focused is None
|
|
|
|
|
|
def test_focus_next_and_previous_with_type_selector(screen: Screen):
|
|
"""Move focus with a selector that matches the currently focused node."""
|
|
screen.set_focus(screen.query_one("#Paul"))
|
|
assert screen.focused.id == "Paul"
|
|
|
|
assert screen.focus_next(Focusable).id == "baz"
|
|
assert screen.focus_next(Focusable).id == "child"
|
|
|
|
assert screen.focus_previous(Focusable).id == "baz"
|
|
assert screen.focus_previous(Focusable).id == "Paul"
|
|
assert screen.focus_previous(Focusable).id == "container1"
|
|
assert screen.focus_previous(Focusable).id == "foo"
|
|
|
|
|
|
def test_focus_next_and_previous_with_str_selector(screen: Screen):
|
|
"""Move focus with a selector that matches the currently focused node."""
|
|
screen.set_focus(screen.query_one("#foo"))
|
|
assert screen.focused.id == "foo"
|
|
|
|
assert screen.focus_next(".a").id == "foo"
|
|
assert screen.focus_next(".c").id == "Paul"
|
|
assert screen.focus_next(".c").id == "child"
|
|
|
|
assert screen.focus_previous(".c").id == "Paul"
|
|
assert screen.focus_previous(".a").id == "foo"
|
|
|
|
|
|
def test_focus_next_and_previous_with_type_selector_without_self():
|
|
"""Test moving the focus with a selector that does not match the currently focused node."""
|
|
app = App()
|
|
app._set_active()
|
|
app.push_screen(Screen())
|
|
|
|
screen = app.screen
|
|
|
|
from textual.containers import Horizontal, Vertical
|
|
from textual.widgets import Button, Switch, Input
|
|
|
|
screen._add_children(
|
|
Vertical(
|
|
Horizontal(
|
|
Input(id="w3"),
|
|
Switch(id="w4"),
|
|
Input(id="w5"),
|
|
Button(id="w6"),
|
|
Switch(id="w7"),
|
|
id="w2",
|
|
),
|
|
Horizontal(
|
|
Button(id="w9"),
|
|
Switch(id="w10"),
|
|
Button(id="w11"),
|
|
Input(id="w12"),
|
|
Input(id="w13"),
|
|
id="w8",
|
|
),
|
|
id="w1",
|
|
)
|
|
)
|
|
|
|
screen.set_focus(screen.query_one("#w3"))
|
|
assert screen.focused.id == "w3"
|
|
|
|
assert screen.focus_next(Button).id == "w6"
|
|
assert screen.focus_next(Switch).id == "w7"
|
|
assert screen.focus_next(Input).id == "w12"
|
|
|
|
assert screen.focus_previous(Button).id == "w11"
|
|
assert screen.focus_previous(Switch).id == "w10"
|
|
assert screen.focus_previous(Button).id == "w9"
|
|
assert screen.focus_previous(Input).id == "w5"
|
|
|
|
|
|
def test_focus_next_and_previous_with_str_selector_without_self(screen: Screen):
|
|
"""Test moving the focus with a selector that does not match the currently focused node."""
|
|
screen.set_focus(screen.query_one("#foo"))
|
|
assert screen.focused.id == "foo"
|
|
|
|
assert screen.focus_next(".c").id == "Paul"
|
|
assert screen.focus_next(".b").id == "baz"
|
|
assert screen.focus_next(".c").id == "child"
|
|
|
|
assert screen.focus_previous(".a").id == "foo"
|
|
assert screen.focus_previous(".a").id == "foo"
|
|
assert screen.focus_previous(".b").id == "baz"
|