feat(viz-platform): complete Sprint 1 - plugin structure and tests
Sprint 1 - viz-platform Plugin completed (13/13 issues): - Commands: 7 files (initial-setup, chart, dashboard, theme, theme-new, theme-css, component) - Agents: 3 files (theme-setup, layout-builder, component-check) - Documentation: README.md, claude-md-integration.md - Tests: 94 tests passing (68-99% coverage) - CHANGELOG updated with completion status Closes: #178, #179, #180, #181, #182 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
283
mcp-servers/viz-platform/tests/test_dmc_tools.py
Normal file
283
mcp-servers/viz-platform/tests/test_dmc_tools.py
Normal file
@@ -0,0 +1,283 @@
|
||||
"""
|
||||
Unit tests for DMC validation tools.
|
||||
"""
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_registry():
|
||||
"""Create a mock component registry"""
|
||||
registry = MagicMock()
|
||||
registry.is_loaded.return_value = True
|
||||
registry.loaded_version = "2.5.1"
|
||||
|
||||
registry.categories = {
|
||||
"buttons": ["Button", "ActionIcon"],
|
||||
"inputs": ["TextInput", "Select"]
|
||||
}
|
||||
|
||||
registry.list_components.return_value = registry.categories
|
||||
registry.get_categories.return_value = ["buttons", "inputs"]
|
||||
|
||||
# Mock Button component
|
||||
registry.get_component.side_effect = lambda name: {
|
||||
"Button": {
|
||||
"description": "Button component",
|
||||
"props": {
|
||||
"variant": {"type": "string", "enum": ["filled", "outline"], "default": "filled"},
|
||||
"color": {"type": "string", "default": "blue"},
|
||||
"size": {"type": "string", "enum": ["xs", "sm", "md", "lg"], "default": "sm"},
|
||||
"disabled": {"type": "boolean", "default": False, "required": False}
|
||||
}
|
||||
},
|
||||
"TextInput": {
|
||||
"description": "Text input",
|
||||
"props": {
|
||||
"value": {"type": "string", "required": True},
|
||||
"placeholder": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}.get(name)
|
||||
|
||||
registry.get_component_props.side_effect = lambda name: {
|
||||
"Button": {
|
||||
"variant": {"type": "string", "enum": ["filled", "outline"], "default": "filled"},
|
||||
"color": {"type": "string", "default": "blue"},
|
||||
"size": {"type": "string", "enum": ["xs", "sm", "md", "lg"], "default": "sm"},
|
||||
"disabled": {"type": "boolean", "default": False}
|
||||
},
|
||||
"TextInput": {
|
||||
"value": {"type": "string", "required": True},
|
||||
"placeholder": {"type": "string"}
|
||||
}
|
||||
}.get(name)
|
||||
|
||||
registry.validate_prop.side_effect = lambda comp, prop, val: (
|
||||
{"valid": True} if prop in ["variant", "color", "size", "disabled", "value", "placeholder"]
|
||||
else {"valid": False, "error": f"Unknown prop '{prop}'"}
|
||||
)
|
||||
|
||||
return registry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dmc_tools(mock_registry):
|
||||
"""Create DMCTools instance with mock registry"""
|
||||
from mcp_server.dmc_tools import DMCTools
|
||||
|
||||
tools = DMCTools(registry=mock_registry)
|
||||
tools._initialized = True
|
||||
return tools
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def uninitialized_tools():
|
||||
"""Create uninitialized DMCTools instance"""
|
||||
from mcp_server.dmc_tools import DMCTools
|
||||
return DMCTools()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_components_all(dmc_tools):
|
||||
"""Test listing all components"""
|
||||
result = await dmc_tools.list_components()
|
||||
|
||||
assert "components" in result
|
||||
assert "categories" in result
|
||||
assert "version" in result
|
||||
assert result["version"] == "2.5.1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_components_by_category(dmc_tools, mock_registry):
|
||||
"""Test listing components by category"""
|
||||
mock_registry.list_components.return_value = {"buttons": ["Button", "ActionIcon"]}
|
||||
|
||||
result = await dmc_tools.list_components(category="buttons")
|
||||
|
||||
assert "buttons" in result["components"]
|
||||
mock_registry.list_components.assert_called_with("buttons")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_components_not_initialized(uninitialized_tools):
|
||||
"""Test listing components when not initialized"""
|
||||
result = await uninitialized_tools.list_components()
|
||||
|
||||
assert "error" in result
|
||||
assert result["total_count"] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_component_props_success(dmc_tools):
|
||||
"""Test getting component props"""
|
||||
result = await dmc_tools.get_component_props("Button")
|
||||
|
||||
assert result["component"] == "Button"
|
||||
assert "props" in result
|
||||
assert result["prop_count"] > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_component_props_not_found(dmc_tools, mock_registry):
|
||||
"""Test getting props for nonexistent component"""
|
||||
mock_registry.get_component.return_value = None
|
||||
|
||||
result = await dmc_tools.get_component_props("Nonexistent")
|
||||
|
||||
assert "error" in result
|
||||
assert "not found" in result["error"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_component_props_not_initialized(uninitialized_tools):
|
||||
"""Test getting props when not initialized"""
|
||||
result = await uninitialized_tools.get_component_props("Button")
|
||||
|
||||
assert "error" in result
|
||||
assert result["prop_count"] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_component_valid(dmc_tools, mock_registry):
|
||||
"""Test validating valid component props"""
|
||||
props = {
|
||||
"variant": "filled",
|
||||
"color": "blue",
|
||||
"size": "md"
|
||||
}
|
||||
|
||||
result = await dmc_tools.validate_component("Button", props)
|
||||
|
||||
assert result["valid"] is True
|
||||
assert len(result["errors"]) == 0
|
||||
assert result["component"] == "Button"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_component_invalid_prop(dmc_tools, mock_registry):
|
||||
"""Test validating with invalid prop name"""
|
||||
mock_registry.validate_prop.side_effect = lambda comp, prop, val: (
|
||||
{"valid": False, "error": f"Unknown prop '{prop}'"} if prop == "unknownProp"
|
||||
else {"valid": True}
|
||||
)
|
||||
|
||||
props = {"unknownProp": "value"}
|
||||
|
||||
result = await dmc_tools.validate_component("Button", props)
|
||||
|
||||
assert result["valid"] is False
|
||||
assert len(result["errors"]) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_component_missing_required(dmc_tools, mock_registry):
|
||||
"""Test validating with missing required prop"""
|
||||
# TextInput has required value prop
|
||||
mock_registry.get_component.return_value = {
|
||||
"props": {
|
||||
"value": {"type": "string", "required": True}
|
||||
}
|
||||
}
|
||||
|
||||
result = await dmc_tools.validate_component("TextInput", {})
|
||||
|
||||
assert result["valid"] is False
|
||||
assert any("required" in e.lower() for e in result["errors"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_component_not_found(dmc_tools, mock_registry):
|
||||
"""Test validating nonexistent component"""
|
||||
mock_registry.get_component.return_value = None
|
||||
|
||||
result = await dmc_tools.validate_component("Nonexistent", {"prop": "value"})
|
||||
|
||||
assert result["valid"] is False
|
||||
assert "Unknown component" in result["errors"][0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_component_not_initialized(uninitialized_tools):
|
||||
"""Test validating when not initialized"""
|
||||
result = await uninitialized_tools.validate_component("Button", {})
|
||||
|
||||
assert result["valid"] is False
|
||||
assert "not initialized" in result["errors"][0].lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_component_skips_special_props(dmc_tools, mock_registry):
|
||||
"""Test that special props (id, children, etc) are skipped"""
|
||||
props = {
|
||||
"id": "my-button",
|
||||
"children": "Click me",
|
||||
"className": "my-class",
|
||||
"style": {"color": "red"},
|
||||
"key": "btn-1"
|
||||
}
|
||||
|
||||
result = await dmc_tools.validate_component("Button", props)
|
||||
|
||||
# Should not error on special props
|
||||
assert result["valid"] is True
|
||||
|
||||
|
||||
def test_find_similar_component(dmc_tools, mock_registry):
|
||||
"""Test finding similar component names"""
|
||||
# Should find Button when given 'button' (case mismatch)
|
||||
similar = dmc_tools._find_similar_component("button")
|
||||
|
||||
assert similar == "Button"
|
||||
|
||||
|
||||
def test_find_similar_component_prefix(dmc_tools, mock_registry):
|
||||
"""Test finding similar component with prefix match"""
|
||||
similar = dmc_tools._find_similar_component("Butt")
|
||||
|
||||
assert similar == "Button"
|
||||
|
||||
|
||||
def test_check_common_mistakes_onclick(dmc_tools):
|
||||
"""Test detection of onclick event handler mistake"""
|
||||
warnings = []
|
||||
dmc_tools._check_common_mistakes("Button", {"onClick": "handler"}, warnings)
|
||||
|
||||
assert len(warnings) > 0
|
||||
assert any("callback" in w.lower() for w in warnings)
|
||||
|
||||
|
||||
def test_check_common_mistakes_class(dmc_tools):
|
||||
"""Test detection of 'class' instead of 'className'"""
|
||||
warnings = []
|
||||
dmc_tools._check_common_mistakes("Button", {"class": "my-class"}, warnings)
|
||||
|
||||
assert len(warnings) > 0
|
||||
assert any("classname" in w.lower() for w in warnings)
|
||||
|
||||
|
||||
def test_check_common_mistakes_button_href(dmc_tools):
|
||||
"""Test detection of Button with href but no component prop"""
|
||||
warnings = []
|
||||
dmc_tools._check_common_mistakes("Button", {"href": "/link"}, warnings)
|
||||
|
||||
assert len(warnings) > 0
|
||||
assert any("component" in w.lower() for w in warnings)
|
||||
|
||||
|
||||
def test_initialize_with_version():
|
||||
"""Test initializing tools with DMC version"""
|
||||
from mcp_server.dmc_tools import DMCTools
|
||||
|
||||
tools = DMCTools()
|
||||
|
||||
with patch('mcp_server.dmc_tools.ComponentRegistry') as MockRegistry:
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.is_loaded.return_value = True
|
||||
MockRegistry.return_value = mock_instance
|
||||
|
||||
result = tools.initialize(dmc_version="2.5.1")
|
||||
|
||||
MockRegistry.assert_called_once_with("2.5.1")
|
||||
assert result is True
|
||||
Reference in New Issue
Block a user