from __future__ import annotations from unittest.mock import Mock from vibe.cli.textual_ui.widgets.banner.banner import Banner, BannerState, _pluralize from vibe.core.config import VibeConfig from vibe.core.config._settings import ModelConfig, ThinkingLevel from vibe.core.skills.manager import SkillManager def _make_mock_config( active_model: str = "test-model", thinking: ThinkingLevel = "off", mcp_servers: list | None = None, ) -> Mock: config = Mock(spec=VibeConfig) config.active_model = active_model config.models = [active_model] config.mcp_servers = mcp_servers or [] config.connectors = [] config.disable_welcome_banner_animation = False config.get_active_model.return_value = ModelConfig( name=active_model, provider="mistral", alias=active_model, thinking=thinking ) return config class TestBannerInitialState: """Test that Banner properly displays initial state including connectors/MCP.""" def test_pluralize(self) -> None: """Test pluralization helper.""" assert _pluralize(0, "model") == "0 models" assert _pluralize(1, "model") == "1 model" assert _pluralize(2, "model") == "2 models" assert _pluralize(0, "MCP server") == "0 MCP servers" assert _pluralize(1, "MCP server") == "1 MCP server" assert _pluralize(2, "connector") == "2 connectors" def test_banner_initial_state_includes_connectors(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config(), skill_manager=skill_manager, connectors_connected=5, connectors_total=5, ) assert banner._initial_state.active_model == "test-model[off]" assert banner._initial_state.models_count == 1 assert ( banner._initial_state.mcp_servers_enabled == 0 ) # No MCP servers configured assert banner._initial_state.mcp_servers_total == 0 assert banner._initial_state.connectors_connected == 5 assert banner._initial_state.connectors_total == 5 assert banner._initial_state.skills_count == 0 def test_banner_initial_state_with_no_connectors(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner(config=_make_mock_config(), skill_manager=skill_manager) assert banner._initial_state.connectors_connected == 0 assert banner._initial_state.connectors_total == 0 def test_banner_shows_thinking_level(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config(thinking="max"), skill_manager=skill_manager ) assert banner._initial_state.active_model == "test-model[max]" def test_format_meta_counts_includes_connectors(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner(config=_make_mock_config(), skill_manager=skill_manager) # Now test _format_meta_counts by setting state with x/y format banner.state = BannerState( models_count=2, mcp_servers_enabled=1, mcp_servers_total=2, connectors_connected=3, connectors_total=3, skills_count=5, ) result = banner._format_meta_counts() assert "2 models" in result assert "3 connectors" in result # When enabled == total, just show count assert "1/2 MCP servers" in result assert "5 skills" in result # Test without connectors banner.state = BannerState( models_count=2, mcp_servers_enabled=1, mcp_servers_total=2, connectors_connected=0, connectors_total=0, skills_count=5, ) result = banner._format_meta_counts() assert "2 models" in result assert "connectors" not in result # Should not appear when 0 assert "1/2 MCP servers" in result assert "5 skills" in result class TestBannerMCPServersCount: """Test that banner correctly counts MCP servers regardless of tool discovery.""" def test_banner_counts_enabled_mcp_servers(self) -> None: """Test that banner counts all enabled MCP servers, not just those with tools.""" from vibe.core.config import MCPServer # Create mock MCP servers - even if they have no tools, they should be counted mock_server1 = Mock(spec=MCPServer) mock_server1.name = "server1" mock_server1.disabled = False mock_server2 = Mock(spec=MCPServer) mock_server2.name = "server2" mock_server2.disabled = False # Create a disabled server that should NOT be counted mock_server3 = Mock(spec=MCPServer) mock_server3.name = "server3" mock_server3.disabled = True skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 # Note: we don't need to mock count_loaded anymore since it's not used banner = Banner( config=_make_mock_config( mcp_servers=[mock_server1, mock_server2, mock_server3] ), skill_manager=skill_manager, ) # Should count only enabled servers (server1 and server2, not server3) assert banner._initial_state.mcp_servers_enabled == 2 assert banner._initial_state.mcp_servers_total == 3 def test_banner_shows_zero_mcp_servers(self) -> None: """Test that banner correctly shows 0 when no MCP servers are configured.""" skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config(mcp_servers=[]), skill_manager=skill_manager ) assert banner._initial_state.mcp_servers_enabled == 0 assert banner._initial_state.mcp_servers_total == 0 def test_banner_shows_disabled_count_in_xy_format(self) -> None: """Test that banner shows x/y format with disabled servers.""" from vibe.core.config import MCPServer skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config( mcp_servers=[ Mock(spec=MCPServer, name="s1", disabled=False), Mock(spec=MCPServer, name="s2", disabled=False), Mock(spec=MCPServer, name="s3", disabled=True), ] ), skill_manager=skill_manager, ) assert banner._initial_state.mcp_servers_enabled == 2 assert banner._initial_state.mcp_servers_total == 3 # Test the formatted output using the initial state banner.state = banner._initial_state result = banner._format_meta_counts() assert "2/3 MCP servers" in result def test_banner_shows_simple_count_when_all_enabled(self) -> None: """Test that banner shows simple count when all MCP servers are enabled.""" from vibe.core.config import MCPServer skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config( mcp_servers=[ Mock(spec=MCPServer, name="s1", disabled=False), Mock(spec=MCPServer, name="s2", disabled=False), ] ), skill_manager=skill_manager, ) assert banner._initial_state.mcp_servers_enabled == 2 assert banner._initial_state.mcp_servers_total == 2 banner.state = banner._initial_state result = banner._format_meta_counts() # When all are enabled, show simple count not x/y assert "2 MCP servers" in result assert "/" not in result # No slash when all enabled class TestBannerConnectorsCount: def test_connectors_count_passed_through(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config(), skill_manager=skill_manager, connectors_connected=3, connectors_total=5, ) assert banner._initial_state.connectors_connected == 3 assert banner._initial_state.connectors_total == 5 class TestBannerHooksCount: def test_hooks_count_passed_through(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config(), skill_manager=skill_manager, hooks_count=4 ) assert banner._initial_state.hooks_count == 4 def test_hooks_count_defaults_to_zero(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner(config=_make_mock_config(), skill_manager=skill_manager) assert banner._initial_state.hooks_count == 0 def test_format_meta_counts_shows_hooks_when_present(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner(config=_make_mock_config(), skill_manager=skill_manager) banner.state = BannerState(models_count=1, skills_count=0, hooks_count=3) result = banner._format_meta_counts() assert "3 hooks" in result def test_format_meta_counts_singular_hook(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner(config=_make_mock_config(), skill_manager=skill_manager) banner.state = BannerState(models_count=1, skills_count=0, hooks_count=1) result = banner._format_meta_counts() assert "1 hook" in result assert "1 hooks" not in result def test_format_meta_counts_hides_hooks_when_zero(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner(config=_make_mock_config(), skill_manager=skill_manager) banner.state = BannerState(models_count=1, skills_count=0, hooks_count=0) result = banner._format_meta_counts() assert "hook" not in result def test_set_state_updates_hooks_count(self) -> None: skill_manager = Mock(spec=SkillManager) skill_manager.custom_skills_count = 0 banner = Banner( config=_make_mock_config(), skill_manager=skill_manager, hooks_count=0 ) banner.set_state( config=_make_mock_config(), skill_manager=skill_manager, hooks_count=7 ) assert banner.state.hooks_count == 7