Major refactor: Consolidate DSS into unified package structure
- Create new dss/ Python package at project root - Move MCP core from tools/dss_mcp/ to dss/mcp/ - Move storage layer from tools/storage/ to dss/storage/ - Move domain logic from dss-mvp1/dss/ to dss/ - Move services from tools/api/services/ to dss/services/ - Move API server to apps/api/ - Move CLI to apps/cli/ - Move Storybook assets to storybook/ - Create unified dss/__init__.py with comprehensive exports - Merge configuration into dss/settings.py (Pydantic-based) - Create pyproject.toml for proper package management - Update startup scripts for new paths - Remove old tools/ and dss-mvp1/ directories Architecture changes: - DSS is now MCP-first with 40+ tools for Claude Code - Clean imports: from dss import Projects, Components, FigmaToolSuite - No more sys.path.insert() hacking - apps/ contains thin application wrappers (API, CLI) - Single unified Python package for all DSS logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,98 +1,82 @@
|
||||
"""
|
||||
Pytest configuration and shared fixtures.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from tools.ingest.base import DesignToken, TokenCollection, TokenType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir():
|
||||
"""Create a temporary directory for tests."""
|
||||
temp_path = tempfile.mkdtemp()
|
||||
yield Path(temp_path)
|
||||
shutil.rmtree(temp_path, ignore_errors=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_css():
|
||||
"""Sample CSS custom properties."""
|
||||
return """
|
||||
:root {
|
||||
--color-primary: #3B82F6;
|
||||
--color-secondary: #10B981;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--font-size-base: 16px;
|
||||
}
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_react_project(tmp_path: Path) -> Path:
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_scss():
|
||||
"""Sample SCSS variables."""
|
||||
return """
|
||||
$primary-color: #3B82F6;
|
||||
$secondary-color: #10B981;
|
||||
$font-family-sans: 'Inter', sans-serif;
|
||||
$font-size-base: 16px;
|
||||
$spacing-md: 16px;
|
||||
Creates a temporary mock React project structure for testing.
|
||||
"""
|
||||
project_dir = tmp_path / "test-project"
|
||||
project_dir.mkdir()
|
||||
|
||||
# Create src directory
|
||||
src_dir = project_dir / "src"
|
||||
src_dir.mkdir()
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_tokens():
|
||||
"""Sample JSON design tokens (W3C format)."""
|
||||
return {
|
||||
"color": {
|
||||
"primary": {
|
||||
"500": {"value": "#3B82F6", "type": "color"},
|
||||
"600": {"value": "#2563EB", "type": "color"}
|
||||
},
|
||||
"secondary": {
|
||||
"500": {"value": "#10B981", "type": "color"}
|
||||
}
|
||||
},
|
||||
"spacing": {
|
||||
"sm": {"value": "8px", "type": "dimension"},
|
||||
"md": {"value": "16px", "type": "dimension"},
|
||||
"lg": {"value": "24px", "type": "dimension"}
|
||||
}
|
||||
}
|
||||
# Create components directory
|
||||
components_dir = src_dir / "components"
|
||||
components_dir.mkdir()
|
||||
|
||||
# Component A
|
||||
(components_dir / "ComponentA.jsx").write_text("""
|
||||
import React from 'react';
|
||||
import './ComponentA.css';
|
||||
|
||||
@pytest.fixture
|
||||
def sample_token_collection():
|
||||
"""Create a sample token collection."""
|
||||
tokens = [
|
||||
DesignToken(name="color.primary", value="#3B82F6", type=TokenType.COLOR),
|
||||
DesignToken(name="color.secondary", value="#10B981", type=TokenType.COLOR),
|
||||
DesignToken(name="spacing.md", value="16px", type=TokenType.SPACING),
|
||||
]
|
||||
return TokenCollection(tokens=tokens, name="Sample Collection")
|
||||
const ComponentA = () => {
|
||||
return <div className="component-a">Component A</div>;
|
||||
};
|
||||
|
||||
export default ComponentA;
|
||||
""")
|
||||
|
||||
@pytest.fixture
|
||||
def tailwind_config_path(temp_dir):
|
||||
"""Create a temporary Tailwind config file."""
|
||||
config_content = """
|
||||
module.exports = {
|
||||
theme: {
|
||||
colors: {
|
||||
blue: '#0000FF',
|
||||
red: '#FF0000'
|
||||
},
|
||||
spacing: {
|
||||
'1': '4px',
|
||||
'2': '8px'
|
||||
}
|
||||
}
|
||||
(components_dir / "ComponentA.css").write_text("""
|
||||
.component-a {
|
||||
color: blue;
|
||||
}
|
||||
"""
|
||||
config_file = temp_dir / "tailwind.config.js"
|
||||
config_file.write_text(config_content)
|
||||
return config_file
|
||||
""")
|
||||
|
||||
# Component B
|
||||
(components_dir / "ComponentB.tsx").write_text("""
|
||||
import React from 'react';
|
||||
import ComponentA from './ComponentA';
|
||||
|
||||
const ComponentB = () => {
|
||||
return (
|
||||
<div>
|
||||
<ComponentA />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentB;
|
||||
""")
|
||||
|
||||
# App.js
|
||||
(src_dir / "App.js").write_text("""
|
||||
import React from 'react';
|
||||
import ComponentB from './components/ComponentB';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<ComponentB />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
""")
|
||||
|
||||
# package.json
|
||||
(project_dir / "package.json").write_text("""
|
||||
{
|
||||
"name": "test-project",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
return project_dir
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
"""
|
||||
Tests for token ingestion from various sources.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from tools.ingest.css import CSSTokenSource
|
||||
from tools.ingest.scss import SCSSTokenSource
|
||||
from tools.ingest.json_tokens import JSONTokenSource
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_css_ingestion(sample_css):
|
||||
"""Test CSS custom property extraction."""
|
||||
parser = CSSTokenSource()
|
||||
result = await parser.extract(sample_css)
|
||||
|
||||
assert len(result.tokens) >= 5
|
||||
assert any(t.name == "color.primary" for t in result.tokens)
|
||||
assert any(t.value == "#3B82F6" for t in result.tokens)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scss_ingestion(sample_scss):
|
||||
"""Test SCSS variable extraction."""
|
||||
parser = SCSSTokenSource()
|
||||
result = await parser.extract(sample_scss)
|
||||
|
||||
assert len(result.tokens) >= 4
|
||||
# SCSS converts $primary-color to primary.color (dashes to dots)
|
||||
assert any(t.name == "primary.color" for t in result.tokens)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_ingestion(sample_json_tokens):
|
||||
"""Test JSON token extraction (W3C format)."""
|
||||
parser = JSONTokenSource()
|
||||
result = await parser.extract(json.dumps(sample_json_tokens))
|
||||
|
||||
assert len(result.tokens) >= 6
|
||||
# Check color tokens
|
||||
primary_tokens = [t for t in result.tokens if "primary" in t.name]
|
||||
assert len(primary_tokens) >= 2
|
||||
|
||||
# Check spacing tokens
|
||||
spacing_tokens = [t for t in result.tokens if "spacing" in t.name]
|
||||
assert len(spacing_tokens) == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_css():
|
||||
"""Test handling of empty CSS."""
|
||||
parser = CSSTokenSource()
|
||||
# Use CSS syntax marker so parser detects as content, not file path
|
||||
result = await parser.extract(":root {}")
|
||||
|
||||
assert len(result.tokens) == 0
|
||||
assert result.name
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_json():
|
||||
"""Test handling of invalid JSON."""
|
||||
parser = JSONTokenSource()
|
||||
|
||||
# Parser wraps JSONDecodeError in ValueError
|
||||
with pytest.raises(ValueError, match="Invalid JSON"):
|
||||
await parser.extract("invalid json{")
|
||||
@@ -1,308 +0,0 @@
|
||||
"""
|
||||
Tests for JSON file storage layer.
|
||||
|
||||
Tests the new json_store module that replaced SQLite.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Temporarily override DATA_DIR for tests
|
||||
import tools.storage.json_store as json_store
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_storage(tmp_path):
|
||||
"""Create temporary storage directory for tests."""
|
||||
# Save original paths
|
||||
original_data_dir = json_store.DATA_DIR
|
||||
original_system_dir = json_store.SYSTEM_DIR
|
||||
original_projects_dir = json_store.PROJECTS_DIR
|
||||
original_teams_dir = json_store.TEAMS_DIR
|
||||
|
||||
# Override with temp paths
|
||||
json_store.DATA_DIR = tmp_path / "data"
|
||||
json_store.SYSTEM_DIR = json_store.DATA_DIR / "_system"
|
||||
json_store.PROJECTS_DIR = json_store.DATA_DIR / "projects"
|
||||
json_store.TEAMS_DIR = json_store.DATA_DIR / "teams"
|
||||
json_store.Cache.CACHE_DIR = json_store.SYSTEM_DIR / "cache"
|
||||
|
||||
# Initialize directories
|
||||
json_store.init_storage()
|
||||
|
||||
yield tmp_path
|
||||
|
||||
# Restore original paths
|
||||
json_store.DATA_DIR = original_data_dir
|
||||
json_store.SYSTEM_DIR = original_system_dir
|
||||
json_store.PROJECTS_DIR = original_projects_dir
|
||||
json_store.TEAMS_DIR = original_teams_dir
|
||||
json_store.Cache.CACHE_DIR = original_system_dir / "cache"
|
||||
|
||||
|
||||
class TestCache:
|
||||
"""Tests for TTL-based cache."""
|
||||
|
||||
def test_cache_set_and_get(self, temp_storage):
|
||||
"""Test basic cache operations."""
|
||||
json_store.Cache.set("test_key", {"foo": "bar"}, ttl=60)
|
||||
result = json_store.Cache.get("test_key")
|
||||
|
||||
assert result == {"foo": "bar"}
|
||||
|
||||
def test_cache_expiry(self, temp_storage):
|
||||
"""Test that expired cache returns None."""
|
||||
import time
|
||||
json_store.Cache.set("expired_key", "value", ttl=1)
|
||||
time.sleep(1.1) # Wait for expiry
|
||||
result = json_store.Cache.get("expired_key")
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_cache_delete(self, temp_storage):
|
||||
"""Test cache deletion."""
|
||||
json_store.Cache.set("delete_me", "value")
|
||||
json_store.Cache.delete("delete_me")
|
||||
result = json_store.Cache.get("delete_me")
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_cache_clear_all(self, temp_storage):
|
||||
"""Test clearing all cache."""
|
||||
json_store.Cache.set("key1", "value1")
|
||||
json_store.Cache.set("key2", "value2")
|
||||
json_store.Cache.clear_all()
|
||||
|
||||
assert json_store.Cache.get("key1") is None
|
||||
assert json_store.Cache.get("key2") is None
|
||||
|
||||
|
||||
class TestProjects:
|
||||
"""Tests for project operations."""
|
||||
|
||||
def test_create_project(self, temp_storage):
|
||||
"""Test project creation."""
|
||||
project = json_store.Projects.create(
|
||||
id="test-project",
|
||||
name="Test Project",
|
||||
description="A test project"
|
||||
)
|
||||
|
||||
assert project["id"] == "test-project"
|
||||
assert project["name"] == "Test Project"
|
||||
assert project["status"] == "active"
|
||||
|
||||
def test_get_project(self, temp_storage):
|
||||
"""Test project retrieval."""
|
||||
json_store.Projects.create(id="get-test", name="Get Test")
|
||||
project = json_store.Projects.get("get-test")
|
||||
|
||||
assert project is not None
|
||||
assert project["name"] == "Get Test"
|
||||
|
||||
def test_list_projects(self, temp_storage):
|
||||
"""Test listing projects."""
|
||||
json_store.Projects.create(id="proj1", name="Project 1")
|
||||
json_store.Projects.create(id="proj2", name="Project 2")
|
||||
|
||||
projects = json_store.Projects.list()
|
||||
|
||||
assert len(projects) == 2
|
||||
|
||||
def test_update_project(self, temp_storage):
|
||||
"""Test project update."""
|
||||
json_store.Projects.create(id="update-test", name="Original")
|
||||
updated = json_store.Projects.update("update-test", name="Updated")
|
||||
|
||||
assert updated["name"] == "Updated"
|
||||
|
||||
def test_delete_project(self, temp_storage):
|
||||
"""Test project deletion (archives)."""
|
||||
json_store.Projects.create(id="delete-test", name="Delete Me")
|
||||
result = json_store.Projects.delete("delete-test")
|
||||
|
||||
assert result is True
|
||||
assert json_store.Projects.get("delete-test") is None
|
||||
|
||||
def test_project_creates_token_structure(self, temp_storage):
|
||||
"""Test that project creation initializes token folders."""
|
||||
json_store.Projects.create(id="token-test", name="Token Test")
|
||||
|
||||
tokens_dir = json_store.PROJECTS_DIR / "token-test" / "tokens"
|
||||
assert tokens_dir.exists()
|
||||
assert (tokens_dir / "colors.json").exists()
|
||||
assert (tokens_dir / "spacing.json").exists()
|
||||
|
||||
|
||||
class TestTokens:
|
||||
"""Tests for token operations."""
|
||||
|
||||
def test_get_all_tokens(self, temp_storage):
|
||||
"""Test getting all tokens for a project."""
|
||||
json_store.Projects.create(id="tokens-proj", name="Tokens Project")
|
||||
tokens = json_store.Tokens.get_all("tokens-proj")
|
||||
|
||||
assert "colors" in tokens
|
||||
assert "spacing" in tokens
|
||||
assert "typography" in tokens
|
||||
|
||||
def test_set_and_get_tokens(self, temp_storage):
|
||||
"""Test setting and getting tokens by type."""
|
||||
json_store.Projects.create(id="set-tokens", name="Set Tokens")
|
||||
|
||||
json_store.Tokens.set_by_type("set-tokens", "colors", {
|
||||
"primary": "#3B82F6",
|
||||
"secondary": "#10B981"
|
||||
})
|
||||
|
||||
colors = json_store.Tokens.get_by_type("set-tokens", "colors")
|
||||
|
||||
assert colors["primary"] == "#3B82F6"
|
||||
assert colors["secondary"] == "#10B981"
|
||||
|
||||
def test_merge_tokens_last_strategy(self, temp_storage):
|
||||
"""Test merging tokens with LAST strategy."""
|
||||
json_store.Projects.create(id="merge-test", name="Merge Test")
|
||||
|
||||
json_store.Tokens.set_by_type("merge-test", "colors", {
|
||||
"primary": "#old",
|
||||
"secondary": "#keep"
|
||||
})
|
||||
|
||||
merged = json_store.Tokens.merge("merge-test", "colors", {
|
||||
"primary": "#new",
|
||||
"tertiary": "#added"
|
||||
}, strategy="LAST")
|
||||
|
||||
assert merged["primary"] == "#new"
|
||||
assert merged["secondary"] == "#keep"
|
||||
assert merged["tertiary"] == "#added"
|
||||
|
||||
|
||||
class TestComponents:
|
||||
"""Tests for component operations."""
|
||||
|
||||
def test_upsert_components(self, temp_storage):
|
||||
"""Test bulk component upsert."""
|
||||
json_store.Projects.create(id="comp-proj", name="Component Project")
|
||||
|
||||
count = json_store.Components.upsert("comp-proj", [
|
||||
{"name": "Button", "properties": {"variant": "primary"}},
|
||||
{"name": "Card", "properties": {"shadow": "md"}}
|
||||
])
|
||||
|
||||
assert count == 2
|
||||
|
||||
def test_list_components(self, temp_storage):
|
||||
"""Test listing components."""
|
||||
json_store.Projects.create(id="list-comp", name="List Components")
|
||||
json_store.Components.upsert("list-comp", [
|
||||
{"name": "Button"},
|
||||
{"name": "Input"}
|
||||
])
|
||||
|
||||
components = json_store.Components.list("list-comp")
|
||||
|
||||
assert len(components) == 2
|
||||
names = [c["name"] for c in components]
|
||||
assert "Button" in names
|
||||
assert "Input" in names
|
||||
|
||||
|
||||
class TestActivityLog:
|
||||
"""Tests for activity logging."""
|
||||
|
||||
def test_log_activity(self, temp_storage):
|
||||
"""Test logging an activity."""
|
||||
json_store.ActivityLog.log(
|
||||
action="test_action",
|
||||
entity_type="test",
|
||||
entity_name="Test Entity",
|
||||
project_id="test-proj"
|
||||
)
|
||||
|
||||
recent = json_store.ActivityLog.recent(limit=1)
|
||||
|
||||
assert len(recent) == 1
|
||||
assert recent[0]["action"] == "test_action"
|
||||
|
||||
def test_activity_auto_category(self, temp_storage):
|
||||
"""Test that activity auto-detects category."""
|
||||
json_store.ActivityLog.log(action="extract_tokens")
|
||||
|
||||
recent = json_store.ActivityLog.recent(limit=1)
|
||||
|
||||
assert recent[0]["category"] == "design_system"
|
||||
|
||||
|
||||
class TestTeams:
|
||||
"""Tests for team operations."""
|
||||
|
||||
def test_create_team(self, temp_storage):
|
||||
"""Test team creation."""
|
||||
team = json_store.Teams.create(
|
||||
id="test-team",
|
||||
name="Test Team",
|
||||
description="A test team"
|
||||
)
|
||||
|
||||
assert team["id"] == "test-team"
|
||||
assert team["name"] == "Test Team"
|
||||
|
||||
def test_add_member(self, temp_storage):
|
||||
"""Test adding team member."""
|
||||
json_store.Teams.create(id="member-team", name="Member Team")
|
||||
json_store.Teams.add_member("member-team", "user-123", "DEVELOPER")
|
||||
|
||||
members = json_store.Teams.get_members("member-team")
|
||||
|
||||
assert len(members) == 1
|
||||
assert members[0]["user_id"] == "user-123"
|
||||
assert members[0]["role"] == "DEVELOPER"
|
||||
|
||||
def test_get_user_role(self, temp_storage):
|
||||
"""Test getting user role in team."""
|
||||
json_store.Teams.create(id="role-team", name="Role Team")
|
||||
json_store.Teams.add_member("role-team", "admin-user", "SUPER_ADMIN")
|
||||
|
||||
role = json_store.Teams.get_user_role("role-team", "admin-user")
|
||||
|
||||
assert role == "SUPER_ADMIN"
|
||||
|
||||
|
||||
class TestSyncHistory:
|
||||
"""Tests for sync history."""
|
||||
|
||||
def test_sync_lifecycle(self, temp_storage):
|
||||
"""Test sync start and complete."""
|
||||
json_store.Projects.create(id="sync-proj", name="Sync Project")
|
||||
|
||||
sync_id = json_store.SyncHistory.start("sync-proj", "tokens")
|
||||
json_store.SyncHistory.complete("sync-proj", sync_id, "success", items_synced=10)
|
||||
|
||||
recent = json_store.SyncHistory.recent("sync-proj", limit=5)
|
||||
|
||||
# Should have both start and complete records
|
||||
completed = [r for r in recent if r.get("status") == "success"]
|
||||
assert len(completed) >= 1
|
||||
|
||||
|
||||
class TestStats:
|
||||
"""Tests for storage statistics."""
|
||||
|
||||
def test_get_stats(self, temp_storage):
|
||||
"""Test getting storage stats."""
|
||||
json_store.Projects.create(id="stats-proj", name="Stats Project")
|
||||
json_store.Teams.create(id="stats-team", name="Stats Team")
|
||||
|
||||
stats = json_store.get_stats()
|
||||
|
||||
# Stats count directories, verify basic structure
|
||||
assert "projects" in stats
|
||||
assert "teams" in stats
|
||||
assert "total_size_mb" in stats
|
||||
assert stats["total_size_mb"] >= 0
|
||||
@@ -1,109 +0,0 @@
|
||||
"""
|
||||
Tests for token merging and conflict resolution.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from tools.ingest.merge import TokenMerger, MergeStrategy
|
||||
from tools.ingest.base import TokenCollection, DesignToken, TokenType
|
||||
|
||||
|
||||
def test_merge_no_conflicts():
|
||||
"""Test merging collections with no conflicts."""
|
||||
col1 = TokenCollection([
|
||||
DesignToken(name="color.red", value="#FF0000", type=TokenType.COLOR)
|
||||
])
|
||||
col2 = TokenCollection([
|
||||
DesignToken(name="color.blue", value="#0000FF", type=TokenType.COLOR)
|
||||
])
|
||||
|
||||
merger = TokenMerger(strategy=MergeStrategy.LAST)
|
||||
result = merger.merge([col1, col2])
|
||||
|
||||
assert len(result.collection.tokens) == 2
|
||||
assert len(result.conflicts) == 0
|
||||
|
||||
|
||||
def test_merge_strategy_first():
|
||||
"""Test FIRST merge strategy."""
|
||||
col1 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#FF0000", type=TokenType.COLOR, source="css")
|
||||
])
|
||||
col2 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#0000FF", type=TokenType.COLOR, source="figma")
|
||||
])
|
||||
|
||||
merger = TokenMerger(strategy=MergeStrategy.FIRST)
|
||||
result = merger.merge([col1, col2])
|
||||
|
||||
assert len(result.collection.tokens) == 1
|
||||
assert len(result.conflicts) == 1
|
||||
# Should keep first value
|
||||
token = result.collection.tokens[0]
|
||||
assert token.value == "#FF0000"
|
||||
assert token.source == "css"
|
||||
|
||||
|
||||
def test_merge_strategy_last():
|
||||
"""Test LAST merge strategy."""
|
||||
col1 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#FF0000", type=TokenType.COLOR, source="css")
|
||||
])
|
||||
col2 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#0000FF", type=TokenType.COLOR, source="figma")
|
||||
])
|
||||
|
||||
merger = TokenMerger(strategy=MergeStrategy.LAST)
|
||||
result = merger.merge([col1, col2])
|
||||
|
||||
assert len(result.collection.tokens) == 1
|
||||
assert len(result.conflicts) == 1
|
||||
# Should keep last value
|
||||
token = result.collection.tokens[0]
|
||||
assert token.value == "#0000FF"
|
||||
assert token.source == "figma"
|
||||
|
||||
|
||||
def test_merge_strategy_prefer_figma():
|
||||
"""Test PREFER_FIGMA merge strategy."""
|
||||
col1 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#FF0000", type=TokenType.COLOR, source="css")
|
||||
])
|
||||
col2 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#3B82F6", type=TokenType.COLOR, source="figma")
|
||||
])
|
||||
col3 = TokenCollection([
|
||||
DesignToken(name="color.primary", value="#0000FF", type=TokenType.COLOR, source="scss")
|
||||
])
|
||||
|
||||
merger = TokenMerger(strategy=MergeStrategy.PREFER_FIGMA)
|
||||
result = merger.merge([col1, col2, col3])
|
||||
|
||||
# Should prefer Figma value
|
||||
token = result.collection.tokens[0]
|
||||
assert token.value == "#3B82F6"
|
||||
assert token.source == "figma"
|
||||
|
||||
|
||||
def test_merge_multiple_collections():
|
||||
"""Test merging many collections."""
|
||||
collections = [
|
||||
TokenCollection([
|
||||
DesignToken(name=f"color.{i}", value=f"#{i:06X}", type=TokenType.COLOR)
|
||||
])
|
||||
for i in range(10)
|
||||
]
|
||||
|
||||
merger = TokenMerger(strategy=MergeStrategy.LAST)
|
||||
result = merger.merge(collections)
|
||||
|
||||
assert len(result.collection.tokens) == 10
|
||||
assert len(result.conflicts) == 0
|
||||
|
||||
|
||||
def test_merge_empty_collections():
|
||||
"""Test merging empty collections."""
|
||||
merger = TokenMerger(strategy=MergeStrategy.LAST)
|
||||
result = merger.merge([])
|
||||
|
||||
assert len(result.collection.tokens) == 0
|
||||
assert len(result.conflicts) == 0
|
||||
45
tests/test_project_analyzer.py
Normal file
45
tests/test_project_analyzer.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import pytest
|
||||
import json
|
||||
from pathlib import Path
|
||||
from dss.analyze.project_analyzer import run_project_analysis
|
||||
|
||||
def test_run_project_analysis(mock_react_project: Path):
|
||||
"""
|
||||
Tests the run_project_analysis function to ensure it creates the analysis graph
|
||||
and that the graph contains the expected file nodes.
|
||||
"""
|
||||
# Run the analysis on the mock project
|
||||
run_project_analysis(str(mock_react_project))
|
||||
|
||||
# Check if the analysis file was created
|
||||
analysis_file = mock_react_project / ".dss" / "analysis_graph.json"
|
||||
assert analysis_file.exists(), "The analysis_graph.json file was not created."
|
||||
|
||||
# Load the analysis data
|
||||
with open(analysis_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Verify the graph structure
|
||||
assert "nodes" in data, "Graph data should contain 'nodes'."
|
||||
assert "links" in data, "Graph data should contain 'links'."
|
||||
|
||||
# Get a list of node IDs (which are the relative file paths)
|
||||
node_ids = [node['id'] for node in data['nodes']]
|
||||
|
||||
# Check for the presence of the files from the mock project
|
||||
expected_files = [
|
||||
"package.json",
|
||||
"src/App.js",
|
||||
"src/components/ComponentA.css",
|
||||
"src/components/ComponentA.jsx",
|
||||
"src/components/ComponentB.tsx",
|
||||
]
|
||||
|
||||
for file_path in expected_files:
|
||||
# Path separators might be different on different OSes, so we normalize
|
||||
normalized_path = str(Path(file_path))
|
||||
assert normalized_path in node_ids, f"Expected file '{normalized_path}' not found in the analysis graph."
|
||||
|
||||
# Verify the number of nodes
|
||||
# There should be exactly the number of files we created
|
||||
assert len(node_ids) == len(expected_files), "The number of nodes in the graph does not match the number of files."
|
||||
Reference in New Issue
Block a user