1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
|
from itertools import cycle
from typing import Type
import pytest
from textual.app import (
ActiveModeError,
App,
ComposeResult,
InvalidModeError,
UnknownModeError,
)
from textual.screen import ModalScreen, Screen
from textual.widgets import Footer, Header, Label
class ScreenBindingsMixin(Screen[None]):
BINDINGS = [
("1", "one", "Mode 1"),
("2", "two", "Mode 2"),
("p", "push", "Push rnd scrn"),
("o", "app.pop_screen", "Pop"),
("r", "remove", "Remove mode 1"),
]
def action_one(self) -> None:
self.app.switch_mode("one")
def action_two(self) -> None:
self.app.switch_mode("two")
def action_fruits(self) -> None:
self.app.switch_mode("fruits")
def action_push(self) -> None:
self.app.push_screen(FruitModal())
class BaseScreen(ScreenBindingsMixin):
def __init__(self, label):
super().__init__()
self.label = label
def compose(self) -> ComposeResult:
yield Header()
yield Label(self.label)
yield Footer()
def action_remove(self) -> None:
self.app.remove_mode("one")
class FruitModal(ModalScreen[str], ScreenBindingsMixin):
BINDINGS = [("d", "dismiss_fruit", "Dismiss")]
def compose(self) -> ComposeResult:
FRUITS = cycle(
"apple mango strawberry banana peach pear melon watermelon".split()
)
yield Label(next(FRUITS))
@pytest.fixture
def ModesApp():
class ModesApp(App[None]):
MODES = {
"one": lambda: BaseScreen("one"),
"two": "screen_two",
}
SCREENS = {
"screen_two": lambda: BaseScreen("two"),
}
def on_mount(self):
self.switch_mode("one")
return ModesApp
async def test_mode_setup(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test():
assert isinstance(app.screen, BaseScreen)
assert str(app.screen.query_one(Label).renderable) == "one"
async def test_switch_mode(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test() as pilot:
await pilot.press("2")
assert str(app.screen.query_one(Label).renderable) == "two"
await pilot.press("1")
assert str(app.screen.query_one(Label).renderable) == "one"
async def test_switch_same_mode(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test() as pilot:
await pilot.press("1")
assert str(app.screen.query_one(Label).renderable) == "one"
await pilot.press("1")
assert str(app.screen.query_one(Label).renderable) == "one"
async def test_switch_unknown_mode(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test():
with pytest.raises(UnknownModeError):
await app.switch_mode("unknown mode here")
async def test_remove_mode(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test() as pilot:
await app.switch_mode("two")
await pilot.pause()
assert str(app.screen.query_one(Label).renderable) == "two"
app.remove_mode("one")
assert "one" not in app._modes
async def test_remove_active_mode(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test():
with pytest.raises(ActiveModeError):
app.remove_mode("one")
async def test_add_mode(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test() as pilot:
app.add_mode("three", lambda: BaseScreen("three"))
await app.switch_mode("three")
await pilot.pause()
assert str(app.screen.query_one(Label).renderable) == "three"
async def test_add_mode_duplicated(ModesApp: Type[App]):
app = ModesApp()
async with app.run_test():
with pytest.raises(InvalidModeError):
app.add_mode("one", lambda: BaseScreen("one"))
async def test_screen_stack_preserved(ModesApp: Type[App]):
fruits = []
N = 5
app = ModesApp()
async with app.run_test() as pilot:
# Build the stack up.
for _ in range(N):
await pilot.press("p")
fruits.append(str(app.query_one(Label).renderable))
assert len(app.screen_stack) == N + 1
# Switch out and back.
await pilot.press("2")
assert len(app.screen_stack) == 1
await pilot.press("1")
# Check the stack.
assert len(app.screen_stack) == N + 1
for _ in range(N):
assert str(app.query_one(Label).renderable) == fruits.pop()
await pilot.press("o")
async def test_multiple_mode_callbacks():
written = []
class LogScreen(Screen[None]):
def __init__(self, value):
super().__init__()
self.value = value
def key_p(self) -> None:
self.app.push_screen(ResultScreen(self.value), written.append)
class ResultScreen(Screen[str]):
def __init__(self, value):
super().__init__()
self.value = value
def key_p(self) -> None:
self.dismiss(self.value)
def key_f(self) -> None:
self.app.switch_mode("first")
def key_o(self) -> None:
self.app.switch_mode("other")
class ModesApp(App[None]):
MODES = {
"first": lambda: LogScreen("first"),
"other": lambda: LogScreen("other"),
}
def on_mount(self) -> None:
self.switch_mode("first")
def key_f(self) -> None:
self.switch_mode("first")
def key_o(self) -> None:
self.switch_mode("other")
app = ModesApp()
async with app.run_test() as pilot:
# Push and dismiss ResultScreen("first")
await pilot.press("p")
await pilot.press("p")
assert written == ["first"]
# Push ResultScreen("first")
await pilot.press("p")
# Switch to LogScreen("other")
await pilot.press("o")
# Push and dismiss ResultScreen("other")
await pilot.press("p")
await pilot.press("p")
assert written == ["first", "other"]
# Go back to ResultScreen("first")
await pilot.press("f")
# Dismiss ResultScreen("first")
await pilot.press("p")
assert written == ["first", "other", "first"]
|