feat(tui): enable multi-component focus and viewport scrolling for Tab 8 and Box panels
This commit is contained in:
+687
-107
File diff suppressed because it is too large
Load Diff
@@ -433,6 +433,154 @@ class TestBoxTUIHeadless(unittest.TestCase):
|
||||
fleet_app._render_footer(h, w)
|
||||
fleet_app._render_help(h, w)
|
||||
|
||||
def test_work_pipeline_multi_component_focus_cycling_and_navigation(self):
|
||||
app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work")
|
||||
app.box_tab = 7
|
||||
|
||||
# 1. Section cycling with Tab, Shift-Tab, l, h
|
||||
self.assertEqual(app.work_focus_component, "tickets")
|
||||
app._handle_key(ord('\t'))
|
||||
self.assertEqual(app.work_focus_component, "prs")
|
||||
app._handle_key(ord('\t'))
|
||||
self.assertEqual(app.work_focus_component, "workers")
|
||||
app._handle_key(ord('\t'))
|
||||
self.assertEqual(app.work_focus_component, "tickets")
|
||||
|
||||
app._handle_key(curses.KEY_BTAB)
|
||||
self.assertEqual(app.work_focus_component, "workers")
|
||||
app._handle_key(curses.KEY_BTAB)
|
||||
self.assertEqual(app.work_focus_component, "prs")
|
||||
|
||||
app._handle_key(ord('l'))
|
||||
self.assertEqual(app.work_focus_component, "workers")
|
||||
app._handle_key(ord('h'))
|
||||
# If on workers, 'h' heals unless not on workers or cycled
|
||||
app.work_focus_component = "prs"
|
||||
app._handle_key(ord('h'))
|
||||
self.assertEqual(app.work_focus_component, "tickets")
|
||||
|
||||
# 2. Navigation with j/k, PageUp/Down, Home/End
|
||||
app.data.work_issues_cache = [{"number": i, "title": f"Issue {i}", "state": "open"} for i in range(10)]
|
||||
app.data.work_prs_cache = [{"number": i, "title": f"PR {i}", "state": "open", "merged": False} for i in range(8)]
|
||||
|
||||
# On tickets
|
||||
app.work_focus_component = "tickets"
|
||||
app.work_tickets_sel_idx = 0
|
||||
app._handle_key(ord('j'))
|
||||
self.assertEqual(app.work_tickets_sel_idx, 1)
|
||||
app._handle_key(curses.KEY_DOWN)
|
||||
self.assertEqual(app.work_tickets_sel_idx, 2)
|
||||
app._handle_key(ord('k'))
|
||||
self.assertEqual(app.work_tickets_sel_idx, 1)
|
||||
app._handle_key(curses.KEY_UP)
|
||||
self.assertEqual(app.work_tickets_sel_idx, 0)
|
||||
app._handle_key(curses.KEY_NPAGE) # PageDown (+5)
|
||||
self.assertEqual(app.work_tickets_sel_idx, 5)
|
||||
app._handle_key(curses.KEY_PPAGE) # PageUp (-5)
|
||||
self.assertEqual(app.work_tickets_sel_idx, 0)
|
||||
app._handle_key(ord('G')) # End
|
||||
self.assertEqual(app.work_tickets_sel_idx, 9)
|
||||
app._handle_key(ord('g')) # Home
|
||||
self.assertEqual(app.work_tickets_sel_idx, 0)
|
||||
|
||||
# On PRs
|
||||
app.work_focus_component = "prs"
|
||||
app.work_prs_sel_idx = 0
|
||||
app._handle_key(ord('j'))
|
||||
self.assertEqual(app.work_prs_sel_idx, 1)
|
||||
app._handle_key(ord('k'))
|
||||
self.assertEqual(app.work_prs_sel_idx, 0)
|
||||
app._handle_key(ord('G'))
|
||||
self.assertEqual(app.work_prs_sel_idx, 7)
|
||||
app._handle_key(ord('g'))
|
||||
self.assertEqual(app.work_prs_sel_idx, 0)
|
||||
|
||||
# On workers
|
||||
app.work_focus_component = "workers"
|
||||
app.work_sel_idx = 0
|
||||
app._handle_key(ord('j'))
|
||||
self.assertEqual(app.work_sel_idx, 1)
|
||||
app._handle_key(ord('k'))
|
||||
self.assertEqual(app.work_sel_idx, 0)
|
||||
app._handle_key(ord('G'))
|
||||
self.assertEqual(app.work_sel_idx, 6)
|
||||
app._handle_key(ord('g'))
|
||||
self.assertEqual(app.work_sel_idx, 0)
|
||||
|
||||
def test_work_pipeline_mouse_bounds_and_section_selection(self):
|
||||
app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work")
|
||||
app.box_tab = 7
|
||||
app.data.work_issues_cache = [{"number": i, "title": f"Ticket {i}", "state": "open"} for i in range(12)]
|
||||
app.data.work_prs_cache = [{"number": i, "title": f"PR {i}", "state": "open", "merged": False} for i in range(8)]
|
||||
|
||||
# Render to calculate bounds
|
||||
app._render_work_view(2, 0, 30, 100)
|
||||
self.assertTrue(hasattr(app, "_work_workers_bounds"))
|
||||
self.assertTrue(hasattr(app, "_work_tickets_bounds"))
|
||||
self.assertTrue(hasattr(app, "_work_prs_bounds"))
|
||||
|
||||
w_start, w_end = app._work_workers_bounds
|
||||
t_start, t_end = app._work_tickets_bounds
|
||||
p_start, p_end = app._work_prs_bounds
|
||||
|
||||
# Click inside tickets section
|
||||
click_bstate = curses.BUTTON1_CLICKED
|
||||
app._handle_mouse(10, t_start + 1, click_bstate)
|
||||
self.assertEqual(app.work_focus_component, "tickets")
|
||||
self.assertEqual(app.work_tickets_sel_idx, 1)
|
||||
|
||||
# Click inside PRs section
|
||||
app._handle_mouse(10, p_start + 2, click_bstate)
|
||||
self.assertEqual(app.work_focus_component, "prs")
|
||||
self.assertEqual(app.work_prs_sel_idx, 2)
|
||||
|
||||
# Click inside workers section
|
||||
app._handle_mouse(10, w_start, click_bstate)
|
||||
self.assertEqual(app.work_focus_component, "workers")
|
||||
self.assertEqual(app.work_sel_idx, 0)
|
||||
|
||||
# Wheel scroll down inside tickets
|
||||
wheel_down = getattr(curses, "BUTTON5_PRESSED", 0x200000)
|
||||
app._handle_mouse(10, t_start + 1, wheel_down)
|
||||
self.assertEqual(app.work_focus_component, "tickets")
|
||||
self.assertEqual(app.work_tickets_sel_idx, 2)
|
||||
|
||||
def test_box_tui_scrollable_tabs_approvals_tmux_dm_logs(self):
|
||||
app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="approvals")
|
||||
|
||||
# Tab 2: Approvals scroll
|
||||
app.box_tab = 2
|
||||
app.data.approvals_cache = [{"node": f"agent{i}", "tool": f"tool{i}", "status": "PENDING"} for i in range(15)]
|
||||
app.approvals_sel_idx = 10
|
||||
app._render_approvals_view(2, 0, 10, 80)
|
||||
self.assertGreater(app.approvals_scroll_start, 0)
|
||||
|
||||
# Tab 4: Tmux scroll
|
||||
app.box_tab = 4
|
||||
app.data.tmux_cache = [f"session_{i}: 1 windows" for i in range(12)]
|
||||
app.tmux_sel_idx = 8
|
||||
app._render_tmux_view(2, 0, 10, 80)
|
||||
self.assertGreater(app.tmux_scroll_start, 0)
|
||||
app._handle_key(ord('k'))
|
||||
self.assertEqual(app.tmux_sel_idx, 7)
|
||||
app._handle_key(ord('j'))
|
||||
self.assertEqual(app.tmux_sel_idx, 8)
|
||||
|
||||
# Tab 5: DM Logs scroll
|
||||
app.box_tab = 5
|
||||
app.data.dm_logs_cache = [{"type": "sent", "agent": "opm", "to": "646", "target": "box", "ts": "2026-10-10T12:00:00", "id": f"m{i}"} for i in range(15)]
|
||||
app.dm_logs_sel_idx = 12
|
||||
app._render_dm_logs_view(2, 0, 10, 80)
|
||||
self.assertGreater(app.dm_logs_scroll_start, 0)
|
||||
app._handle_key(ord('k'))
|
||||
self.assertEqual(app.dm_logs_sel_idx, 11)
|
||||
app._handle_key(ord('j'))
|
||||
self.assertEqual(app.dm_logs_sel_idx, 12)
|
||||
app._handle_key(ord('g'))
|
||||
self.assertEqual(app.dm_logs_sel_idx, 0)
|
||||
app._handle_key(ord('G'))
|
||||
self.assertEqual(app.dm_logs_sel_idx, 14)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user