Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
0
dss-mvp1/tests/integration/__init__.py
Normal file
0
dss-mvp1/tests/integration/__init__.py
Normal file
172
dss-mvp1/tests/integration/test_figma_mock.py
Normal file
172
dss-mvp1/tests/integration/test_figma_mock.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""Integration tests for Figma wrapper using mock API responses"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from dss.tools.figma import FigmaWrapper, FigmaAPIError
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestFigmaWrapperWithMocks:
|
||||
"""Test Figma wrapper with mocked API responses"""
|
||||
|
||||
def test_init_with_mock_credentials(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test initializing FigmaWrapper with mock credentials"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
assert wrapper.api_token == mock_figma_token
|
||||
assert wrapper.file_key == mock_figma_file_key
|
||||
assert wrapper.headers["X-Figma-Token"] == mock_figma_token
|
||||
|
||||
def test_extract_themes_with_mock_response(
|
||||
self,
|
||||
mock_figma_token,
|
||||
mock_figma_file_key,
|
||||
mock_figma_response
|
||||
):
|
||||
"""Test extracting themes from mock Figma response"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
# Mock the API call
|
||||
with patch.object(wrapper, 'get_variables', return_value=mock_figma_response):
|
||||
themes = wrapper.extract_themes()
|
||||
|
||||
# Should extract Light and Dark themes
|
||||
assert "Light" in themes
|
||||
assert "Dark" in themes
|
||||
|
||||
# Check Light theme has tokens
|
||||
light_theme = themes["Light"]
|
||||
assert light_theme.name == "DSS Light"
|
||||
assert len(light_theme.tokens) > 0
|
||||
|
||||
# Check Dark theme has tokens
|
||||
dark_theme = themes["Dark"]
|
||||
assert dark_theme.name == "DSS Dark"
|
||||
assert len(dark_theme.tokens) > 0
|
||||
|
||||
def test_build_mode_map(self, mock_figma_token, mock_figma_file_key, mock_figma_response):
|
||||
"""Test building mode ID to theme name mapping"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
variable_collections = mock_figma_response["meta"]["variableCollections"]
|
||||
mode_map = wrapper._build_mode_map(variable_collections)
|
||||
|
||||
# Should map mode IDs to names
|
||||
assert "1:0" in mode_map
|
||||
assert mode_map["1:0"] == "Light"
|
||||
assert "1:1" in mode_map
|
||||
assert mode_map["1:1"] == "Dark"
|
||||
|
||||
def test_convert_figma_color_to_rgb(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test converting Figma color format to RGB"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
# Figma color format: {r: 0-1, g: 0-1, b: 0-1, a: 0-1}
|
||||
figma_color = {
|
||||
"r": 0.0,
|
||||
"g": 0.4,
|
||||
"b": 0.8,
|
||||
"a": 1.0
|
||||
}
|
||||
|
||||
rgb_string = wrapper._format_value(figma_color, "color")
|
||||
|
||||
# Should convert to rgb(0, 102, 204)
|
||||
assert "rgb(" in rgb_string
|
||||
assert "0" in rgb_string # Red component
|
||||
assert "102" in rgb_string # Green component
|
||||
assert "204" in rgb_string # Blue component
|
||||
|
||||
def test_handle_api_errors(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test handling Figma API errors"""
|
||||
import requests
|
||||
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
# Mock 403 Forbidden error
|
||||
with patch('requests.get') as mock_get:
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 403
|
||||
|
||||
# Properly simulate HTTPError
|
||||
http_error = requests.exceptions.HTTPError()
|
||||
http_error.response = mock_response
|
||||
mock_response.raise_for_status.side_effect = http_error
|
||||
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
with pytest.raises(FigmaAPIError) as exc_info:
|
||||
wrapper.get_variables()
|
||||
|
||||
assert "Invalid Figma API token" in str(exc_info.value) or "403" in str(exc_info.value)
|
||||
|
||||
def test_handle_404_not_found(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test handling file not found error"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
# Mock 404 Not Found error
|
||||
with patch('requests.get') as mock_get:
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 404
|
||||
mock_response.raise_for_status.side_effect = Exception("404 Not Found")
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
with pytest.raises(FigmaAPIError) as exc_info:
|
||||
wrapper.get_variables()
|
||||
|
||||
assert "not found" in str(exc_info.value).lower()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestFigmaTokenConversion:
|
||||
"""Test Figma token type conversions"""
|
||||
|
||||
def test_map_figma_type_to_dtcg(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test mapping Figma types to DTCG types"""
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
assert wrapper._map_figma_type_to_dtcg("COLOR") == "color"
|
||||
assert wrapper._map_figma_type_to_dtcg("FLOAT") == "number"
|
||||
assert wrapper._map_figma_type_to_dtcg("STRING") == "string"
|
||||
assert wrapper._map_figma_type_to_dtcg("BOOLEAN") == "boolean"
|
||||
assert wrapper._map_figma_type_to_dtcg("UNKNOWN") == "other"
|
||||
|
||||
def test_map_dtcg_type_to_category(self, mock_figma_token, mock_figma_file_key):
|
||||
"""Test mapping DTCG types to DSS categories"""
|
||||
from dss.models.theme import TokenCategory
|
||||
|
||||
wrapper = FigmaWrapper(
|
||||
api_token=mock_figma_token,
|
||||
file_key=mock_figma_file_key
|
||||
)
|
||||
|
||||
assert wrapper._map_dtcg_type_to_category("color") == TokenCategory.COLOR
|
||||
assert wrapper._map_dtcg_type_to_category("dimension") == TokenCategory.SPACING
|
||||
assert wrapper._map_dtcg_type_to_category("fontSize") == TokenCategory.TYPOGRAPHY
|
||||
assert wrapper._map_dtcg_type_to_category("shadow") == TokenCategory.SHADOW
|
||||
assert wrapper._map_dtcg_type_to_category("unknown") == TokenCategory.OTHER
|
||||
129
dss-mvp1/tests/integration/test_style_dictionary.py
Normal file
129
dss-mvp1/tests/integration/test_style_dictionary.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Integration tests for Style Dictionary wrapper"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from dss.tools.style_dictionary import StyleDictionaryWrapper
|
||||
from dss.themes import get_default_light_theme
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestStyleDictionaryIntegration:
|
||||
"""Test Style Dictionary integration"""
|
||||
|
||||
def test_convert_tokens_to_css_vars(self):
|
||||
"""Test converting DSS theme to CSS custom properties"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css_output = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Check that CSS output is valid
|
||||
assert ":root {" in css_output
|
||||
assert "--background:" in css_output
|
||||
assert "--primary:" in css_output
|
||||
assert "--space-md:" in css_output
|
||||
assert "}" in css_output
|
||||
|
||||
def test_convert_theme_to_sd_format(self):
|
||||
"""Test converting DSS theme to Style Dictionary format"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
sd_format = sd._convert_theme_to_sd_format(theme)
|
||||
|
||||
# Check structure
|
||||
assert "color" in sd_format
|
||||
assert "spacing" in sd_format
|
||||
assert "radius" in sd_format
|
||||
assert "typography" in sd_format
|
||||
|
||||
# Check color tokens
|
||||
assert "background" in sd_format["color"]
|
||||
assert "primary" in sd_format["color"]
|
||||
assert sd_format["color"]["primary"]["value"] == "oklch(0.65 0.18 250)"
|
||||
|
||||
# Check spacing tokens
|
||||
assert "space-md" in sd_format["spacing"]
|
||||
assert sd_format["spacing"]["space-md"]["value"] == "16px"
|
||||
|
||||
def test_create_sd_config_css(self):
|
||||
"""Test creating Style Dictionary config for CSS output"""
|
||||
sd = StyleDictionaryWrapper()
|
||||
build_path = Path("/tmp/test")
|
||||
|
||||
config = sd._create_sd_config("css", build_path)
|
||||
|
||||
assert "source" in config
|
||||
assert "platforms" in config
|
||||
assert "css" in config["platforms"]
|
||||
assert config["platforms"]["css"]["transformGroup"] == "css"
|
||||
assert config["platforms"]["css"]["files"][0]["format"] == "css/variables"
|
||||
|
||||
def test_create_sd_config_scss(self):
|
||||
"""Test creating Style Dictionary config for SCSS output"""
|
||||
sd = StyleDictionaryWrapper()
|
||||
build_path = Path("/tmp/test")
|
||||
|
||||
config = sd._create_sd_config("scss", build_path)
|
||||
|
||||
assert "scss" in config["platforms"]
|
||||
assert config["platforms"]["scss"]["transformGroup"] == "scss"
|
||||
assert config["platforms"]["scss"]["files"][0]["format"] == "scss/variables"
|
||||
|
||||
def test_create_sd_config_json(self):
|
||||
"""Test creating Style Dictionary config for JSON output"""
|
||||
sd = StyleDictionaryWrapper()
|
||||
build_path = Path("/tmp/test")
|
||||
|
||||
config = sd._create_sd_config("json", build_path)
|
||||
|
||||
assert "json" in config["platforms"]
|
||||
assert config["platforms"]["json"]["files"][0]["format"] == "json/nested"
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_transform_theme_to_css(self):
|
||||
"""Test full transformation to CSS (requires npm)"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
result = sd.transform_theme(theme, output_format="css")
|
||||
|
||||
# Check result structure
|
||||
assert "success" in result
|
||||
assert "output_format" in result
|
||||
assert result["output_format"] == "css"
|
||||
|
||||
# If style-dictionary is installed, check output
|
||||
if result["success"]:
|
||||
assert "files" in result
|
||||
assert "theme.css" in result["files"]
|
||||
css_content = result["files"]["theme.css"]
|
||||
assert "--" in css_content # CSS variables
|
||||
|
||||
def test_css_var_naming_convention(self):
|
||||
"""Test that CSS variable names follow kebab-case convention"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css_output = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Check naming conventions
|
||||
assert "--space-md:" in css_output
|
||||
assert "--radius-sm:" in css_output
|
||||
assert "--text-base:" in css_output
|
||||
|
||||
# Should not have camelCase or underscores
|
||||
assert "spacemd" not in css_output.lower()
|
||||
assert "space_md" not in css_output
|
||||
|
||||
def test_css_output_includes_comments(self):
|
||||
"""Test that CSS output includes token descriptions as comments"""
|
||||
theme = get_default_light_theme()
|
||||
sd = StyleDictionaryWrapper()
|
||||
|
||||
css_output = sd.convert_tokens_to_css_vars(theme)
|
||||
|
||||
# Check for comments
|
||||
assert "/*" in css_output
|
||||
assert "Main background color" in css_output
|
||||
assert "Primary brand color" in css_output
|
||||
Reference in New Issue
Block a user