1
- import pytest
2
-
3
1
from textual .app import App , ComposeResult
4
2
from textual .containers import Container , ScrollableContainer
5
- from textual .screen import Screen
6
3
from textual .widget import Widget
7
4
from textual .widgets import Button , Label
8
5
@@ -19,34 +16,23 @@ class ChildrenFocusableOnly(Widget, can_focus=False, can_focus_children=True):
19
16
pass
20
17
21
18
22
- @pytest .fixture
23
- def screen () -> Screen :
24
- app = App ()
25
-
26
- with app ._context ():
27
- app .push_screen (Screen ())
28
-
29
- screen = app .screen
19
+ class FocusTestApp (App ):
20
+ AUTO_FOCUS = None
30
21
22
+ def compose (self ) -> ComposeResult :
31
23
# The classes even/odd alternate along the focus chain.
32
24
# The classes in/out identify nested widgets.
33
- screen ._add_children (
34
- Focusable (id = "foo" , classes = "a" ),
35
- NonFocusable (id = "bar" ),
36
- Focusable (Focusable (id = "Paul" , classes = "c" ), id = "container1" , classes = "b" ),
37
- NonFocusable (Focusable (id = "Jessica" , classes = "a" ), id = "container2" ),
38
- Focusable (id = "baz" , classes = "b" ),
39
- ChildrenFocusableOnly (Focusable (id = "child" , classes = "c" )),
40
- )
25
+ yield Focusable (id = "foo" , classes = "a" )
26
+ yield NonFocusable (id = "bar" )
27
+ yield Focusable (Focusable (id = "Paul" , classes = "c" ), id = "container1" , classes = "b" )
28
+ yield NonFocusable (Focusable (id = "Jessica" , classes = "a" ), id = "container2" )
29
+ yield Focusable (id = "baz" , classes = "b" )
30
+ yield ChildrenFocusableOnly (Focusable (id = "child" , classes = "c" ))
41
31
42
- return screen
43
32
44
-
45
- def test_focus_chain ():
33
+ async def test_focus_chain ():
46
34
app = App ()
47
- with app ._context ():
48
- app .push_screen (Screen ())
49
-
35
+ async with app .run_test ():
50
36
screen = app .screen
51
37
52
38
# Check empty focus chain
@@ -65,7 +51,7 @@ def test_focus_chain():
65
51
assert focus_chain == ["foo" , "container1" , "Paul" , "baz" , "child" ]
66
52
67
53
68
- def test_allow_focus ():
54
+ async def test_allow_focus ():
69
55
"""Test allow_focus and allow_focus_children are called and the result used."""
70
56
focusable_allow_focus_called = False
71
57
non_focusable_allow_focus_called = False
@@ -92,9 +78,7 @@ def allow_focus_children(self) -> bool:
92
78
93
79
app = App ()
94
80
95
- with app ._context ():
96
- app .push_screen (Screen ())
97
-
81
+ async with app .run_test ():
98
82
app .screen ._add_children (
99
83
Focusable (id = "foo" ),
100
84
NonFocusable (id = "bar" ),
@@ -106,93 +90,119 @@ def allow_focus_children(self) -> bool:
106
90
assert non_focusable_allow_focus_called
107
91
108
92
109
- def test_focus_next_and_previous (screen : Screen ):
110
- assert screen .focus_next ().id == "foo"
111
- assert screen .focus_next ().id == "container1"
112
- assert screen .focus_next ().id == "Paul"
113
- assert screen .focus_next ().id == "baz"
114
- assert screen .focus_next ().id == "child"
93
+ async def test_focus_next_and_previous ():
94
+ app = FocusTestApp ()
95
+ async with app .run_test ():
96
+ screen = app .screen
97
+
98
+ assert screen .focus_next ().id == "foo"
99
+ assert screen .focus_next ().id == "container1"
100
+ assert screen .focus_next ().id == "Paul"
101
+ assert screen .focus_next ().id == "baz"
102
+ assert screen .focus_next ().id == "child"
115
103
116
- assert screen .focus_previous ().id == "baz"
117
- assert screen .focus_previous ().id == "Paul"
118
- assert screen .focus_previous ().id == "container1"
119
- assert screen .focus_previous ().id == "foo"
104
+ assert screen .focus_previous ().id == "baz"
105
+ assert screen .focus_previous ().id == "Paul"
106
+ assert screen .focus_previous ().id == "container1"
107
+ assert screen .focus_previous ().id == "foo"
120
108
121
109
122
- def test_focus_next_wrap_around (screen : Screen ):
110
+ async def test_focus_next_wrap_around ():
123
111
"""Ensure focusing the next widget wraps around the focus chain."""
124
- screen .set_focus (screen .query_one ("#child" ))
125
- assert screen .focused .id == "child"
112
+ app = FocusTestApp ()
113
+ async with app .run_test ():
114
+ screen = app .screen
126
115
127
- assert screen .focus_next ().id == "foo"
116
+ screen .set_focus (screen .query_one ("#child" ))
117
+ assert screen .focused .id == "child"
128
118
119
+ assert screen .focus_next ().id == "foo"
129
120
130
- def test_focus_previous_wrap_around (screen : Screen ):
121
+
122
+ async def test_focus_previous_wrap_around ():
131
123
"""Ensure focusing the previous widget wraps around the focus chain."""
132
- screen .set_focus (screen .query_one ("#foo" ))
133
- assert screen .focused .id == "foo"
124
+ app = FocusTestApp ()
125
+ async with app .run_test ():
126
+ screen = app .screen
127
+
128
+ screen .set_focus (screen .query_one ("#foo" ))
129
+ assert screen .focused .id == "foo"
134
130
135
- assert screen .focus_previous ().id == "child"
131
+ assert screen .focus_previous ().id == "child"
136
132
137
133
138
- def test_wrap_around_selector (screen : Screen ):
134
+ async def test_wrap_around_selector ():
139
135
"""Ensure moving focus in both directions wraps around the focus chain."""
140
- screen .set_focus (screen .query_one ("#foo" ))
141
- assert screen .focused .id == "foo"
136
+ app = FocusTestApp ()
137
+ async with app .run_test ():
138
+ screen = app .screen
139
+
140
+ screen .set_focus (screen .query_one ("#foo" ))
141
+ assert screen .focused .id == "foo"
142
142
143
- assert screen .focus_previous ("#Paul" ).id == "Paul"
144
- assert screen .focus_next ("#foo" ).id == "foo"
143
+ assert screen .focus_previous ("#Paul" ).id == "Paul"
144
+ assert screen .focus_next ("#foo" ).id == "foo"
145
145
146
146
147
- def test_no_focus_empty_selector (screen : Screen ):
147
+ async def test_no_focus_empty_selector ():
148
148
"""Ensure focus is cleared when selector matches nothing."""
149
- assert screen .focus_next ("#bananas" ) is None
150
- assert screen .focus_previous ("#bananas" ) is None
149
+ app = FocusTestApp ()
150
+ async with app .run_test ():
151
+ screen = app .screen
152
+
153
+ assert screen .focus_next ("#bananas" ) is None
154
+ assert screen .focus_previous ("#bananas" ) is None
151
155
152
- screen .set_focus (screen .query_one ("#foo" ))
153
- assert screen .focused is not None
154
- assert screen .focus_next ("#bananas" ) is None
155
- assert screen .focused is None
156
+ screen .set_focus (screen .query_one ("#foo" ))
157
+ assert screen .focused is not None
158
+ assert screen .focus_next ("#bananas" ) is None
159
+ assert screen .focused is None
156
160
157
- screen .set_focus (screen .query_one ("#foo" ))
158
- assert screen .focused is not None
159
- assert screen .focus_previous ("#bananas" ) is None
160
- assert screen .focused is None
161
+ screen .set_focus (screen .query_one ("#foo" ))
162
+ assert screen .focused is not None
163
+ assert screen .focus_previous ("#bananas" ) is None
164
+ assert screen .focused is None
161
165
162
166
163
- def test_focus_next_and_previous_with_type_selector (screen : Screen ):
167
+ async def test_focus_next_and_previous_with_type_selector ():
164
168
"""Move focus with a selector that matches the currently focused node."""
165
- screen .set_focus (screen .query_one ("#Paul" ))
166
- assert screen .focused .id == "Paul"
169
+ app = FocusTestApp ()
170
+ async with app .run_test ():
171
+ screen = app .screen
167
172
168
- assert screen .focus_next ( Focusable ). id == "baz"
169
- assert screen .focus_next ( Focusable ) .id == "child "
173
+ screen .set_focus ( screen . query_one ( "#Paul" ))
174
+ assert screen .focused .id == "Paul "
170
175
171
- assert screen .focus_previous (Focusable ).id == "baz"
172
- assert screen .focus_previous (Focusable ).id == "Paul"
173
- assert screen .focus_previous (Focusable ).id == "container1"
174
- assert screen .focus_previous (Focusable ).id == "foo"
176
+ assert screen .focus_next (Focusable ).id == "baz"
177
+ assert screen .focus_next (Focusable ).id == "child"
175
178
179
+ assert screen .focus_previous (Focusable ).id == "baz"
180
+ assert screen .focus_previous (Focusable ).id == "Paul"
181
+ assert screen .focus_previous (Focusable ).id == "container1"
182
+ assert screen .focus_previous (Focusable ).id == "foo"
176
183
177
- def test_focus_next_and_previous_with_str_selector (screen : Screen ):
184
+
185
+ async def test_focus_next_and_previous_with_str_selector ():
178
186
"""Move focus with a selector that matches the currently focused node."""
179
- screen .set_focus (screen .query_one ("#foo" ))
180
- assert screen .focused .id == "foo"
187
+ app = FocusTestApp ()
188
+ async with app .run_test ():
189
+ screen = app .screen
181
190
182
- assert screen .focus_next (".a" ).id == "foo"
183
- assert screen .focus_next (".c" ).id == "Paul"
184
- assert screen .focus_next (".c" ).id == "child"
191
+ screen .set_focus (screen .query_one ("#foo" ))
192
+ assert screen .focused .id == "foo"
185
193
186
- assert screen .focus_previous (".c" ).id == "Paul"
187
- assert screen .focus_previous (".a" ).id == "foo"
194
+ assert screen .focus_next (".a" ).id == "foo"
195
+ assert screen .focus_next (".c" ).id == "Paul"
196
+ assert screen .focus_next (".c" ).id == "child"
188
197
198
+ assert screen .focus_previous (".c" ).id == "Paul"
199
+ assert screen .focus_previous (".a" ).id == "foo"
189
200
190
- def test_focus_next_and_previous_with_type_selector_without_self ():
201
+
202
+ async def test_focus_next_and_previous_with_type_selector_without_self ():
191
203
"""Test moving the focus with a selector that does not match the currently focused node."""
192
204
app = App ()
193
- with app ._context ():
194
- app .push_screen (Screen ())
195
-
205
+ async with app .run_test ():
196
206
screen = app .screen
197
207
198
208
from textual .containers import Horizontal , VerticalScroll
@@ -233,18 +243,22 @@ def test_focus_next_and_previous_with_type_selector_without_self():
233
243
assert screen .focus_previous (Input ).id == "w5"
234
244
235
245
236
- def test_focus_next_and_previous_with_str_selector_without_self (screen : Screen ):
246
+ async def test_focus_next_and_previous_with_str_selector_without_self ():
237
247
"""Test moving the focus with a selector that does not match the currently focused node."""
238
- screen .set_focus (screen .query_one ("#foo" ))
239
- assert screen .focused .id == "foo"
248
+ app = FocusTestApp ()
249
+ async with app .run_test ():
250
+ screen = app .screen
251
+
252
+ screen .set_focus (screen .query_one ("#foo" ))
253
+ assert screen .focused .id == "foo"
240
254
241
- assert screen .focus_next (".c" ).id == "Paul"
242
- assert screen .focus_next (".b" ).id == "baz"
243
- assert screen .focus_next (".c" ).id == "child"
255
+ assert screen .focus_next (".c" ).id == "Paul"
256
+ assert screen .focus_next (".b" ).id == "baz"
257
+ assert screen .focus_next (".c" ).id == "child"
244
258
245
- assert screen .focus_previous (".a" ).id == "foo"
246
- assert screen .focus_previous (".a" ).id == "foo"
247
- assert screen .focus_previous (".b" ).id == "baz"
259
+ assert screen .focus_previous (".a" ).id == "foo"
260
+ assert screen .focus_previous (".a" ).id == "foo"
261
+ assert screen .focus_previous (".b" ).id == "baz"
248
262
249
263
250
264
async def test_focus_does_not_move_to_invisible_widgets ():
0 commit comments