aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_scenarios.py78
1 files changed, 78 insertions, 0 deletions
diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py
index ff45281..d7c1f14 100644
--- a/tests/test_scenarios.py
+++ b/tests/test_scenarios.py
@@ -998,6 +998,84 @@ async def s_structs(c: Ctx):
c.check("Esc closes the struct editor", not isinstance(app.screen, StructEditor))
+@scenario("struct_filter")
+async def s_struct_filter(c: Ctx):
+ app = c.app
+ await c.press("ctrl+t")
+ if not await c.wait(lambda: isinstance(app.screen, StructEditor), 10):
+ c.check("Ctrl+T opens the struct editor", False,
+ f"screen={type(app.screen).__name__}")
+ return
+ se = app.screen
+ await c.wait(lambda: bool(se._structs), 15)
+ total = len(se._structs)
+ ol = se.query_one("#se-list", OptionList)
+ inp = se.query_one("#se-filter", Input)
+ # Something past the first row, so a filter that "works" by doing nothing
+ # can't pass: its own name must survive and the others must not.
+ target = se._structs[min(2, total - 1)].name
+ q = "".join(ch for ch in target.lower() if ch.isalnum())[:3]
+
+ ol.focus()
+ await c.press("slash")
+ opened = await c.wait(lambda: inp.display and app.focused is inp, 5)
+ c.check("'/' from the list opens the struct filter", opened,
+ f"display={inp.display} focus={getattr(app.focused, 'id', None)}")
+ for ch in q:
+ await c.press(ch)
+ await c.wait(lambda: len(se._structs) < total, 5)
+ c.check("typing fuzzy-filters the struct list",
+ 0 < len(se._structs) < total and
+ any(s.name == target for s in se._structs),
+ f"q={q!r} {len(se._structs)}/{total}")
+ cap = str(se.query_one("#se-title", Static).render())
+ c.check("the caption counts what the filter kept",
+ f"{len(se._structs)}/{total}" in cap, f"caption={cap!r}")
+
+ # 'd' is the delete binding on this screen: in the prompt it must be a
+ # character, not a destructive verb aimed at the highlighted struct.
+ await c.press("d")
+ await c.pause(0.05)
+ c.check("'d' in the filter types instead of deleting",
+ isinstance(app.screen, StructEditor) and inp.value == q + "d",
+ f"screen={type(app.screen).__name__} value={inp.value!r}")
+ await c.press("backspace")
+ await c.wait(lambda: inp.value == q, 5)
+
+ # Arrows drive the list while the prompt keeps focus (symbol-palette feel).
+ before = ol.highlighted
+ await c.press("down")
+ await c.pause(0.05)
+ c.check("arrows move the list while the filter has focus",
+ app.focused is inp and (ol.highlighted != before
+ or ol.option_count == 1),
+ f"{before} -> {ol.highlighted} of {ol.option_count}")
+
+ sel = se._structs[ol.highlighted or 0].name
+ ta = se.query_one(TextArea)
+ ta.text = ""
+ await c.press("enter")
+ loaded = await c.wait(lambda: sel in ta.text, 15)
+ c.check("Enter in the filter loads the highlighted struct", loaded,
+ f"want {sel!r} in {ta.text[:40]!r}")
+
+ # Esc backs out one level at a time: definition -> filter -> dialog.
+ await c.press("escape")
+ await c.wait(lambda: app.focused is ol, 5)
+ c.check("Esc leaves the definition for the list", app.focused is ol,
+ f"focus={getattr(app.focused, 'id', None)}")
+ await c.press("escape")
+ cleared = await c.wait(lambda: len(se._structs) == total, 5)
+ c.check("Esc clears the filter instead of closing",
+ cleared and not inp.display and isinstance(app.screen, StructEditor),
+ f"n={len(se._structs)}/{total} display={inp.display}")
+ await c.press("escape")
+ await c.wait(lambda: not isinstance(app.screen, StructEditor), 10)
+ c.check("a third Esc closes the editor",
+ not isinstance(app.screen, StructEditor),
+ f"screen={type(app.screen).__name__}")
+
+
@scenario("open_default_view")
async def s_open(c: Ctx):
app = c.app