from types import SimpleNamespace import pytest def test_build_filter_bar_text_includes_search_context(tui_modules: SimpleNamespace) -> None: text = tui_modules.cli_tui_search.TUI_SEARCH_SERVICE.build_filter_bar_text( tui_modules.cli_tui_search.JOB_FILTER_OPTIONS, 'failed', search_query='sync', search_placeholder='按任务名搜索', search_suggestions=['sync-job', 'sync-user'], ) assert '当前筛选 · 失败' in text assert '搜索器 · [/] 按任务名搜索' in text assert '当前搜索 · sync | [Backspace] 清空' in text assert '候选建议 · sync-job sync-user' in text def test_tui_search_service_builds_filter_context_and_section_filtering( tui_modules: SimpleNamespace, ) -> None: service = tui_modules.cli_tui_search.TuiSearchService( tui_modules.cli_tui_search.TuiSearchSuggestionProviderRegistry( providers={ 'jobs': tui_modules.cli_tui_search.SearchSuggestionProviderSpec( '按任务名搜索', lambda incomplete: ['sync-user', 'sync-role', 'cleanup'], ), 'app': tui_modules.cli_tui_search.SearchSuggestionProviderSpec('按分区或内容搜索'), } ) ) option = service.resolve_filter_option(tui_modules.cli_tui_search.JOB_FILTER_OPTIONS, 'failed') filter_bar = service.build_filter_bar_text( tui_modules.cli_tui_search.JOB_FILTER_OPTIONS, 'failed', search_query='sync', search_placeholder='按任务名搜索', search_suggestions=['sync-user', 'sync-role'], ) search_context = service.resolve_search_context('jobs', 'sync') default_context = service.resolve_search_context('app', '总览', default_suggestions=['总览判断', '应用配置']) sections = service.filter_detail_sections( [ tui_modules.cli_tui_adapters.DetailSectionSnapshot(title='总览判断', status='ok', lines=['应用正常']), tui_modules.cli_tui_adapters.DetailSectionSnapshot(title='路由摘要', status='warn', lines=['同步任务接口']), ], '同步', ) assert option is not None assert option.key == 'failed' assert '当前筛选 · 失败' in filter_bar assert search_context is not None assert search_context.placeholder == '按任务名搜索' assert search_context.suggestions[:2] == ['sync-user', 'sync-role'] assert default_context is not None assert default_context.suggestions == ['总览判断'] assert [section.title for section in sections] == ['路由摘要'] def test_tui_search_service_skips_provider_when_query_is_empty( tui_modules: SimpleNamespace, ) -> None: provider_calls: list[str] = [] def record_provider(incomplete: str) -> list[str]: provider_calls.append(incomplete) return ['site_name'] service = tui_modules.cli_tui_search.TuiSearchService( tui_modules.cli_tui_search.TuiSearchSuggestionProviderRegistry( providers={ 'configs': tui_modules.cli_tui_search.SearchSuggestionProviderSpec( '按配置键搜索', record_provider, ) } ) ) search_context = service.resolve_search_context('configs', '') assert search_context is not None assert search_context.placeholder == '按配置键搜索' assert search_context.suggestions == [] assert provider_calls == [] def test_search_highlight_helper_wraps_matches(tui_modules: SimpleNamespace) -> None: highlighted = tui_modules.cli_tui_search.TUI_SEARCH_HIGHLIGHTER.highlight('sync-user job', 'sync') assert highlighted == '【sync】-user job' def test_dashboard_screen_support_builds_summary_signal_and_status_track( tui_modules: SimpleNamespace, ) -> None: support = tui_modules.cli_tui_dashboard.DashboardScreenSupport() snapshot = tui_modules.cli_tui_app.DashboardSnapshot( env='dev', metrics=[], panels=[ tui_modules.cli_tui_adapters.DashboardPanelSnapshot(title='应用', status='ok', lines=['ok']), tui_modules.cli_tui_adapters.DashboardPanelSnapshot(title='任务', status='warn', lines=['warn']), tui_modules.cli_tui_adapters.DashboardPanelSnapshot(title='数据库', status='fail', lines=['fail']), ], ) summary = support.build_summary_text(snapshot) status_track = support.build_status_track(snapshot) signal_lines = support.build_signal_lines(snapshot) assert '共 3 个面板' in summary assert status_track == 'o!x' assert any('失败 01' in line for line in signal_lines) def test_dashboard_screen_focus_service_moves_and_scrolls_between_focus_targets( tui_modules: SimpleNamespace, ) -> None: focus_service = tui_modules.cli_tui_dashboard.DashboardScreenFocusService() focused: list[str] = [] sidebar = SimpleNamespace(focus=lambda: focused.append('sidebar')) workspace_main = SimpleNamespace(focus=lambda: focused.append('main')) fake_app = SimpleNamespace(focused=None) fake_screen = SimpleNamespace( app=fake_app, query_one=lambda selector, widget_type=None: { tui_modules.cli_tui_widgets.WorkspaceSidebar: sidebar, '#workspace-main': workspace_main, }[selector], ) focus_service.move_focus(fake_screen, 1) fake_app.focused = sidebar focus_service.move_focus(fake_screen, 1) fake_app.focused = object() scroll_target = focus_service.get_scroll_target(fake_screen) assert focused == ['sidebar', 'main'] assert scroll_target is workspace_main @pytest.mark.asyncio async def test_dashboard_grid_expands_to_fit_status_panels( monkeypatch: pytest.MonkeyPatch, tui_modules: SimpleNamespace, ) -> None: expected_dashboard_panel_count = 8 min_expanded_dashboard_grid_height = 20 snapshot = tui_modules.cli_tui_app.DashboardSnapshot( env='dev', metrics=[], panels=[ tui_modules.cli_tui_adapters.DashboardPanelSnapshot( title=f'面板 {index + 1}', status='ok' if index % 2 == 0 else 'warn', lines=[ '## 巡检摘要', f'当前面板: {index + 1}', '状态说明: 这里需要展示多行内容', '', '## 建议动作', '进入对应分区继续查看详情', ], ) for index in range(8) ], ) monkeypatch.setitem( tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors, 'dashboard', lambda env: snapshot ) app = tui_modules.cli_tui_app.ShierfengTuiApp('dev') async with app.run_test(size=(160, 60)) as pilot: await pilot.pause() await pilot.pause() grid = app.screen.query_one('#dashboard-grid') panels = list( app.screen.query(tui_modules.cli_tui_widgets.StatusPanel).results(tui_modules.cli_tui_widgets.StatusPanel) ) assert len(panels) == expected_dashboard_panel_count assert grid.virtual_size.height > min_expanded_dashboard_grid_height assert all(panel.content_size.height > 0 for panel in panels) assert len({panel.region.height for panel in panels}) == 1