Skip to content

Commit a3d0175

Browse files
author
codejunkie99
committed
test: cover dashboard tui navigation [skip ci]
1 parent 62854d2 commit a3d0175

2 files changed

Lines changed: 128 additions & 24 deletions

File tree

harness_manager/dashboard_tui.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,33 @@ def _open_section(section: str, target_root: Path, stack_root: Path) -> None:
812812
_pause()
813813

814814

815+
def _run_interactive(stdscr: Any, target: Path, stack: Path, curses_mod: Any) -> None:
816+
try:
817+
curses_mod.curs_set(0)
818+
except Exception:
819+
pass
820+
stdscr.keypad(True)
821+
section_idx = 0
822+
while True:
823+
_draw(stdscr, section_idx, target, stack, curses_mod)
824+
key = stdscr.getch()
825+
if key in (ord("q"), 27):
826+
return
827+
if key in (ord("j"), curses_mod.KEY_DOWN):
828+
section_idx = min(len(SECTIONS) - 1, section_idx + 1)
829+
elif key in (ord("k"), curses_mod.KEY_UP):
830+
section_idx = max(0, section_idx - 1)
831+
elif key == ord("r"):
832+
continue
833+
elif key in (10, 13, curses_mod.KEY_ENTER):
834+
section = SECTIONS[section_idx]
835+
curses_mod.endwin()
836+
try:
837+
_open_section(section, target, stack)
838+
finally:
839+
stdscr.clear()
840+
841+
815842
def run(target_root: Path | str, stack_root: Path | str, plain: bool = False) -> int:
816843
target = _logical_path(target_root)
817844
stack = Path(stack_root)
@@ -825,30 +852,7 @@ def run(target_root: Path | str, stack_root: Path | str, plain: bool = False) ->
825852
return 0
826853

827854
def _main(stdscr: Any) -> None:
828-
try:
829-
curses.curs_set(0)
830-
except Exception:
831-
pass
832-
stdscr.keypad(True)
833-
section_idx = 0
834-
while True:
835-
_draw(stdscr, section_idx, target, stack, curses)
836-
key = stdscr.getch()
837-
if key in (ord("q"), 27):
838-
return
839-
if key in (ord("j"), curses.KEY_DOWN):
840-
section_idx = min(len(SECTIONS) - 1, section_idx + 1)
841-
elif key in (ord("k"), curses.KEY_UP):
842-
section_idx = max(0, section_idx - 1)
843-
elif key == ord("r"):
844-
continue
845-
elif key in (10, 13, curses.KEY_ENTER):
846-
section = SECTIONS[section_idx]
847-
curses.endwin()
848-
try:
849-
_open_section(section, target, stack)
850-
finally:
851-
stdscr.clear()
855+
_run_interactive(stdscr, target, stack, curses)
852856

853857
curses.wrapper(_main)
854858
return 0

test_dashboard_tui.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,58 @@
99
ROOT = Path(__file__).resolve().parent
1010

1111

12+
class FakeCurses:
13+
A_BOLD = 1
14+
KEY_DOWN = 258
15+
KEY_UP = 259
16+
KEY_ENTER = 343
17+
18+
def __init__(self):
19+
self.cursor_values = []
20+
self.endwin_count = 0
21+
22+
def curs_set(self, value):
23+
self.cursor_values.append(value)
24+
25+
def endwin(self):
26+
self.endwin_count += 1
27+
28+
29+
class FakeScreen:
30+
def __init__(self, keys, height=32, width=100):
31+
self.keys = list(keys)
32+
self.height = height
33+
self.width = width
34+
self.keypad_enabled = None
35+
self.clear_count = 0
36+
self.current = {}
37+
self.snapshots = []
38+
39+
def getmaxyx(self):
40+
return self.height, self.width
41+
42+
def keypad(self, enabled):
43+
self.keypad_enabled = enabled
44+
45+
def erase(self):
46+
self.current = {}
47+
48+
def clear(self):
49+
self.clear_count += 1
50+
self.current = {}
51+
52+
def addstr(self, y, x, text, attr=0):
53+
self.current[(y, x)] = text
54+
55+
def refresh(self):
56+
self.snapshots.append(dict(self.current))
57+
58+
def getch(self):
59+
if self.keys:
60+
return self.keys.pop(0)
61+
return ord("q")
62+
63+
1264
class DashboardTuiTest(unittest.TestCase):
1365
def make_project(self, root: Path) -> None:
1466
agent = root / ".agent"
@@ -229,6 +281,54 @@ def test_bare_installed_project_stays_script_safe_without_tty(self):
229281
self.assertEqual(result.returncode, 0, result.stderr)
230282
self.assertIn("open dashboard", result.stdout)
231283

284+
def test_interactive_dashboard_keypress_navigation(self):
285+
from harness_manager import dashboard_tui
286+
287+
curses = FakeCurses()
288+
screen = FakeScreen(
289+
[
290+
curses.KEY_DOWN,
291+
curses.KEY_DOWN,
292+
ord("r"),
293+
curses.KEY_UP,
294+
ord("q"),
295+
]
296+
)
297+
with tempfile.TemporaryDirectory() as tmp:
298+
project = Path(tmp)
299+
self.make_project(project)
300+
301+
dashboard_tui._run_interactive(screen, project, ROOT, curses)
302+
303+
self.assertEqual(curses.cursor_values, [0])
304+
self.assertTrue(screen.keypad_enabled)
305+
headings = [snapshot.get((4, 21)) for snapshot in screen.snapshots]
306+
self.assertEqual(headings, ["Overview", "Adapters", "Doctor", "Doctor", "Adapters"])
307+
308+
def test_interactive_dashboard_enter_opens_selected_section(self):
309+
from harness_manager import dashboard_tui
310+
311+
curses = FakeCurses()
312+
screen = FakeScreen([curses.KEY_DOWN, curses.KEY_ENTER, ord("q")])
313+
opened = []
314+
original_open_section = dashboard_tui._open_section
315+
316+
def fake_open_section(section, target_root, stack_root):
317+
opened.append((section, target_root, stack_root))
318+
319+
with tempfile.TemporaryDirectory() as tmp:
320+
project = Path(tmp)
321+
self.make_project(project)
322+
try:
323+
dashboard_tui._open_section = fake_open_section
324+
dashboard_tui._run_interactive(screen, project, ROOT, curses)
325+
finally:
326+
dashboard_tui._open_section = original_open_section
327+
328+
self.assertEqual(opened, [("Adapters", project, ROOT)])
329+
self.assertEqual(curses.endwin_count, 1)
330+
self.assertEqual(screen.clear_count, 1)
331+
232332

233333
if __name__ == "__main__":
234334
unittest.main()

0 commit comments

Comments
 (0)