Initial commit
This commit is contained in:
17
skills/ui-styling/scripts/requirements.txt
Normal file
17
skills/ui-styling/scripts/requirements.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# UI Styling Skill Dependencies
|
||||
# Python 3.10+ required
|
||||
|
||||
# No Python package dependencies - uses only standard library
|
||||
|
||||
# Testing dependencies (dev)
|
||||
pytest>=8.0.0
|
||||
pytest-cov>=4.1.0
|
||||
pytest-mock>=3.12.0
|
||||
|
||||
# Note: This skill works with shadcn/ui and Tailwind CSS
|
||||
# Requires Node.js and package managers:
|
||||
# - Node.js 18+: https://nodejs.org/
|
||||
# - npm (comes with Node.js)
|
||||
#
|
||||
# shadcn/ui CLI is installed per-project:
|
||||
# npx shadcn-ui@latest init
|
||||
292
skills/ui-styling/scripts/shadcn_add.py
Normal file
292
skills/ui-styling/scripts/shadcn_add.py
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
shadcn/ui Component Installer
|
||||
|
||||
Add shadcn/ui components to project with automatic dependency handling.
|
||||
Wraps shadcn CLI for programmatic component installation.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class ShadcnInstaller:
|
||||
"""Handle shadcn/ui component installation."""
|
||||
|
||||
def __init__(self, project_root: Optional[Path] = None, dry_run: bool = False):
|
||||
"""
|
||||
Initialize installer.
|
||||
|
||||
Args:
|
||||
project_root: Project root directory (default: current directory)
|
||||
dry_run: If True, show actions without executing
|
||||
"""
|
||||
self.project_root = project_root or Path.cwd()
|
||||
self.dry_run = dry_run
|
||||
self.components_json = self.project_root / "components.json"
|
||||
|
||||
def check_shadcn_config(self) -> bool:
|
||||
"""
|
||||
Check if shadcn is initialized in project.
|
||||
|
||||
Returns:
|
||||
True if components.json exists
|
||||
"""
|
||||
return self.components_json.exists()
|
||||
|
||||
def get_installed_components(self) -> List[str]:
|
||||
"""
|
||||
Get list of already installed components.
|
||||
|
||||
Returns:
|
||||
List of installed component names
|
||||
"""
|
||||
if not self.check_shadcn_config():
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(self.components_json) as f:
|
||||
config = json.load(f)
|
||||
|
||||
components_dir = self.project_root / config.get("aliases", {}).get(
|
||||
"components", "components"
|
||||
).replace("@/", "")
|
||||
ui_dir = components_dir / "ui"
|
||||
|
||||
if not ui_dir.exists():
|
||||
return []
|
||||
|
||||
return [f.stem for f in ui_dir.glob("*.tsx") if f.is_file()]
|
||||
except (json.JSONDecodeError, KeyError, OSError):
|
||||
return []
|
||||
|
||||
def add_components(
|
||||
self, components: List[str], overwrite: bool = False
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
Add shadcn/ui components.
|
||||
|
||||
Args:
|
||||
components: List of component names to add
|
||||
overwrite: If True, overwrite existing components
|
||||
|
||||
Returns:
|
||||
Tuple of (success, message)
|
||||
"""
|
||||
if not components:
|
||||
return False, "No components specified"
|
||||
|
||||
if not self.check_shadcn_config():
|
||||
return (
|
||||
False,
|
||||
"shadcn not initialized. Run 'npx shadcn@latest init' first",
|
||||
)
|
||||
|
||||
# Check which components already exist
|
||||
installed = self.get_installed_components()
|
||||
already_installed = [c for c in components if c in installed]
|
||||
|
||||
if already_installed and not overwrite:
|
||||
return (
|
||||
False,
|
||||
f"Components already installed: {', '.join(already_installed)}. "
|
||||
"Use --overwrite to reinstall",
|
||||
)
|
||||
|
||||
# Build command
|
||||
cmd = ["npx", "shadcn@latest", "add"] + components
|
||||
|
||||
if overwrite:
|
||||
cmd.append("--overwrite")
|
||||
|
||||
if self.dry_run:
|
||||
return True, f"Would run: {' '.join(cmd)}"
|
||||
|
||||
# Execute command
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
success_msg = f"Successfully added components: {', '.join(components)}"
|
||||
if result.stdout:
|
||||
success_msg += f"\n\nOutput:\n{result.stdout}"
|
||||
|
||||
return True, success_msg
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = f"Failed to add components: {e.stderr or e.stdout or str(e)}"
|
||||
return False, error_msg
|
||||
except FileNotFoundError:
|
||||
return False, "npx not found. Ensure Node.js is installed"
|
||||
|
||||
def add_all_components(self, overwrite: bool = False) -> tuple[bool, str]:
|
||||
"""
|
||||
Add all available shadcn/ui components.
|
||||
|
||||
Args:
|
||||
overwrite: If True, overwrite existing components
|
||||
|
||||
Returns:
|
||||
Tuple of (success, message)
|
||||
"""
|
||||
if not self.check_shadcn_config():
|
||||
return (
|
||||
False,
|
||||
"shadcn not initialized. Run 'npx shadcn@latest init' first",
|
||||
)
|
||||
|
||||
cmd = ["npx", "shadcn@latest", "add", "--all"]
|
||||
|
||||
if overwrite:
|
||||
cmd.append("--overwrite")
|
||||
|
||||
if self.dry_run:
|
||||
return True, f"Would run: {' '.join(cmd)}"
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
success_msg = "Successfully added all components"
|
||||
if result.stdout:
|
||||
success_msg += f"\n\nOutput:\n{result.stdout}"
|
||||
|
||||
return True, success_msg
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = f"Failed to add all components: {e.stderr or e.stdout or str(e)}"
|
||||
return False, error_msg
|
||||
except FileNotFoundError:
|
||||
return False, "npx not found. Ensure Node.js is installed"
|
||||
|
||||
def list_installed(self) -> tuple[bool, str]:
|
||||
"""
|
||||
List installed components.
|
||||
|
||||
Returns:
|
||||
Tuple of (success, message with component list)
|
||||
"""
|
||||
if not self.check_shadcn_config():
|
||||
return False, "shadcn not initialized"
|
||||
|
||||
installed = self.get_installed_components()
|
||||
|
||||
if not installed:
|
||||
return True, "No components installed"
|
||||
|
||||
return True, f"Installed components:\n" + "\n".join(f" - {c}" for c in sorted(installed))
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Add shadcn/ui components to your project",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Add single component
|
||||
python shadcn_add.py button
|
||||
|
||||
# Add multiple components
|
||||
python shadcn_add.py button card dialog
|
||||
|
||||
# Add all components
|
||||
python shadcn_add.py --all
|
||||
|
||||
# Overwrite existing components
|
||||
python shadcn_add.py button --overwrite
|
||||
|
||||
# Dry run (show what would be done)
|
||||
python shadcn_add.py button card --dry-run
|
||||
|
||||
# List installed components
|
||||
python shadcn_add.py --list
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"components",
|
||||
nargs="*",
|
||||
help="Component names to add (e.g., button, card, dialog)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="Add all available components",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--overwrite",
|
||||
action="store_true",
|
||||
help="Overwrite existing components",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be done without executing",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--list",
|
||||
action="store_true",
|
||||
help="List installed components",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--project-root",
|
||||
type=Path,
|
||||
help="Project root directory (default: current directory)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialize installer
|
||||
installer = ShadcnInstaller(
|
||||
project_root=args.project_root,
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
|
||||
# Handle list command
|
||||
if args.list:
|
||||
success, message = installer.list_installed()
|
||||
print(message)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
# Handle add all command
|
||||
if args.all:
|
||||
success, message = installer.add_all_components(overwrite=args.overwrite)
|
||||
print(message)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
# Handle add specific components
|
||||
if not args.components:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
success, message = installer.add_components(
|
||||
args.components,
|
||||
overwrite=args.overwrite,
|
||||
)
|
||||
|
||||
print(message)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
456
skills/ui-styling/scripts/tailwind_config_gen.py
Normal file
456
skills/ui-styling/scripts/tailwind_config_gen.py
Normal file
@@ -0,0 +1,456 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tailwind CSS Configuration Generator
|
||||
|
||||
Generate tailwind.config.js/ts with custom theme configuration.
|
||||
Supports colors, fonts, spacing, breakpoints, and plugin recommendations.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class TailwindConfigGenerator:
|
||||
"""Generate Tailwind CSS configuration files."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
typescript: bool = True,
|
||||
framework: str = "react",
|
||||
output_path: Optional[Path] = None,
|
||||
):
|
||||
"""
|
||||
Initialize generator.
|
||||
|
||||
Args:
|
||||
typescript: If True, generate .ts config, else .js
|
||||
framework: Framework name (react, vue, svelte, nextjs)
|
||||
output_path: Output file path (default: auto-detect)
|
||||
"""
|
||||
self.typescript = typescript
|
||||
self.framework = framework
|
||||
self.output_path = output_path or self._default_output_path()
|
||||
self.config: Dict[str, Any] = self._base_config()
|
||||
|
||||
def _default_output_path(self) -> Path:
|
||||
"""Determine default output path."""
|
||||
ext = "ts" if self.typescript else "js"
|
||||
return Path.cwd() / f"tailwind.config.{ext}"
|
||||
|
||||
def _base_config(self) -> Dict[str, Any]:
|
||||
"""Create base configuration structure."""
|
||||
return {
|
||||
"darkMode": ["class"],
|
||||
"content": self._default_content_paths(),
|
||||
"theme": {
|
||||
"extend": {}
|
||||
},
|
||||
"plugins": []
|
||||
}
|
||||
|
||||
def _default_content_paths(self) -> List[str]:
|
||||
"""Get default content paths for framework."""
|
||||
paths = {
|
||||
"react": [
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
"./index.html",
|
||||
],
|
||||
"vue": [
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
"./index.html",
|
||||
],
|
||||
"svelte": [
|
||||
"./src/**/*.{svelte,js,ts}",
|
||||
"./src/app.html",
|
||||
],
|
||||
"nextjs": [
|
||||
"./app/**/*.{js,ts,jsx,tsx}",
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
}
|
||||
return paths.get(self.framework, paths["react"])
|
||||
|
||||
def add_colors(self, colors: Dict[str, str]) -> None:
|
||||
"""
|
||||
Add custom colors to theme.
|
||||
|
||||
Args:
|
||||
colors: Dict of color_name: color_value
|
||||
Value can be hex (#3b82f6) or variable (hsl(var(--primary)))
|
||||
"""
|
||||
if "colors" not in self.config["theme"]["extend"]:
|
||||
self.config["theme"]["extend"]["colors"] = {}
|
||||
|
||||
self.config["theme"]["extend"]["colors"].update(colors)
|
||||
|
||||
def add_color_palette(self, name: str, base_color: str) -> None:
|
||||
"""
|
||||
Add full color palette (50-950 shades) for a base color.
|
||||
|
||||
Args:
|
||||
name: Color name (e.g., 'brand', 'primary')
|
||||
base_color: Base color in oklch format or hex
|
||||
"""
|
||||
# For simplicity, use CSS variable approach
|
||||
if "colors" not in self.config["theme"]["extend"]:
|
||||
self.config["theme"]["extend"]["colors"] = {}
|
||||
|
||||
self.config["theme"]["extend"]["colors"][name] = {
|
||||
"50": f"var(--color-{name}-50)",
|
||||
"100": f"var(--color-{name}-100)",
|
||||
"200": f"var(--color-{name}-200)",
|
||||
"300": f"var(--color-{name}-300)",
|
||||
"400": f"var(--color-{name}-400)",
|
||||
"500": f"var(--color-{name}-500)",
|
||||
"600": f"var(--color-{name}-600)",
|
||||
"700": f"var(--color-{name}-700)",
|
||||
"800": f"var(--color-{name}-800)",
|
||||
"900": f"var(--color-{name}-900)",
|
||||
"950": f"var(--color-{name}-950)",
|
||||
}
|
||||
|
||||
def add_fonts(self, fonts: Dict[str, List[str]]) -> None:
|
||||
"""
|
||||
Add custom font families.
|
||||
|
||||
Args:
|
||||
fonts: Dict of font_type: [font_names]
|
||||
e.g., {'sans': ['Inter', 'system-ui', 'sans-serif']}
|
||||
"""
|
||||
if "fontFamily" not in self.config["theme"]["extend"]:
|
||||
self.config["theme"]["extend"]["fontFamily"] = {}
|
||||
|
||||
self.config["theme"]["extend"]["fontFamily"].update(fonts)
|
||||
|
||||
def add_spacing(self, spacing: Dict[str, str]) -> None:
|
||||
"""
|
||||
Add custom spacing values.
|
||||
|
||||
Args:
|
||||
spacing: Dict of name: value
|
||||
e.g., {'18': '4.5rem', 'navbar': '4rem'}
|
||||
"""
|
||||
if "spacing" not in self.config["theme"]["extend"]:
|
||||
self.config["theme"]["extend"]["spacing"] = {}
|
||||
|
||||
self.config["theme"]["extend"]["spacing"].update(spacing)
|
||||
|
||||
def add_breakpoints(self, breakpoints: Dict[str, str]) -> None:
|
||||
"""
|
||||
Add custom breakpoints.
|
||||
|
||||
Args:
|
||||
breakpoints: Dict of name: width
|
||||
e.g., {'3xl': '1920px', 'tablet': '768px'}
|
||||
"""
|
||||
if "screens" not in self.config["theme"]["extend"]:
|
||||
self.config["theme"]["extend"]["screens"] = {}
|
||||
|
||||
self.config["theme"]["extend"]["screens"].update(breakpoints)
|
||||
|
||||
def add_plugins(self, plugins: List[str]) -> None:
|
||||
"""
|
||||
Add plugin requirements.
|
||||
|
||||
Args:
|
||||
plugins: List of plugin names
|
||||
e.g., ['@tailwindcss/typography', '@tailwindcss/forms']
|
||||
"""
|
||||
for plugin in plugins:
|
||||
if plugin not in self.config["plugins"]:
|
||||
self.config["plugins"].append(plugin)
|
||||
|
||||
def recommend_plugins(self) -> List[str]:
|
||||
"""
|
||||
Get plugin recommendations based on configuration.
|
||||
|
||||
Returns:
|
||||
List of recommended plugin package names
|
||||
"""
|
||||
recommendations = []
|
||||
|
||||
# Always recommend animation plugin
|
||||
recommendations.append("tailwindcss-animate")
|
||||
|
||||
# Framework-specific recommendations
|
||||
if self.framework == "nextjs":
|
||||
recommendations.append("@tailwindcss/typography")
|
||||
|
||||
return recommendations
|
||||
|
||||
def generate_config_string(self) -> str:
|
||||
"""
|
||||
Generate configuration file content.
|
||||
|
||||
Returns:
|
||||
Configuration file as string
|
||||
"""
|
||||
if self.typescript:
|
||||
return self._generate_typescript()
|
||||
return self._generate_javascript()
|
||||
|
||||
def _generate_typescript(self) -> str:
|
||||
"""Generate TypeScript configuration."""
|
||||
plugins_str = self._format_plugins()
|
||||
|
||||
config_json = json.dumps(self.config, indent=2)
|
||||
|
||||
# Remove plugin array from JSON (we'll add it with require())
|
||||
config_obj = self.config.copy()
|
||||
config_obj.pop("plugins", None)
|
||||
config_json = json.dumps(config_obj, indent=2)
|
||||
|
||||
return f"""import type {{ Config }} from 'tailwindcss'
|
||||
|
||||
const config: Config = {{
|
||||
{self._indent_json(config_json, 1)}
|
||||
plugins: [{plugins_str}],
|
||||
}}
|
||||
|
||||
export default config
|
||||
"""
|
||||
|
||||
def _generate_javascript(self) -> str:
|
||||
"""Generate JavaScript configuration."""
|
||||
plugins_str = self._format_plugins()
|
||||
|
||||
config_obj = self.config.copy()
|
||||
config_obj.pop("plugins", None)
|
||||
config_json = json.dumps(config_obj, indent=2)
|
||||
|
||||
return f"""/** @type {{import('tailwindcss').Config}} */
|
||||
module.exports = {{
|
||||
{self._indent_json(config_json, 1)}
|
||||
plugins: [{plugins_str}],
|
||||
}}
|
||||
"""
|
||||
|
||||
def _format_plugins(self) -> str:
|
||||
"""Format plugins array for config."""
|
||||
if not self.config["plugins"]:
|
||||
return ""
|
||||
|
||||
plugin_requires = [
|
||||
f"require('{plugin}')" for plugin in self.config["plugins"]
|
||||
]
|
||||
return ", ".join(plugin_requires)
|
||||
|
||||
def _indent_json(self, json_str: str, level: int) -> str:
|
||||
"""Add indentation to JSON string."""
|
||||
indent = " " * level
|
||||
lines = json_str.split("\n")
|
||||
# Skip first and last lines (braces)
|
||||
indented = [indent + line for line in lines[1:-1]]
|
||||
return "\n".join(indented)
|
||||
|
||||
def write_config(self) -> tuple[bool, str]:
|
||||
"""
|
||||
Write configuration to file.
|
||||
|
||||
Returns:
|
||||
Tuple of (success, message)
|
||||
"""
|
||||
try:
|
||||
config_content = self.generate_config_string()
|
||||
|
||||
self.output_path.write_text(config_content)
|
||||
|
||||
return True, f"Configuration written to {self.output_path}"
|
||||
|
||||
except OSError as e:
|
||||
return False, f"Failed to write config: {e}"
|
||||
|
||||
def validate_config(self) -> tuple[bool, str]:
|
||||
"""
|
||||
Validate configuration.
|
||||
|
||||
Returns:
|
||||
Tuple of (valid, message)
|
||||
"""
|
||||
# Check content paths exist
|
||||
if not self.config["content"]:
|
||||
return False, "No content paths specified"
|
||||
|
||||
# Check if extending empty theme
|
||||
if not self.config["theme"]["extend"]:
|
||||
return True, "Warning: No theme extensions defined"
|
||||
|
||||
return True, "Configuration valid"
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate Tailwind CSS configuration",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Generate TypeScript config for Next.js
|
||||
python tailwind_config_gen.py --framework nextjs
|
||||
|
||||
# Generate JavaScript config with custom colors
|
||||
python tailwind_config_gen.py --js --colors brand:#3b82f6 accent:#8b5cf6
|
||||
|
||||
# Add custom fonts
|
||||
python tailwind_config_gen.py --fonts display:"Playfair Display,serif"
|
||||
|
||||
# Add custom spacing and breakpoints
|
||||
python tailwind_config_gen.py --spacing navbar:4rem --breakpoints 3xl:1920px
|
||||
|
||||
# Add recommended plugins
|
||||
python tailwind_config_gen.py --plugins
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["react", "vue", "svelte", "nextjs"],
|
||||
default="react",
|
||||
help="Target framework (default: react)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--js",
|
||||
action="store_true",
|
||||
help="Generate JavaScript config instead of TypeScript",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
help="Output file path",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--colors",
|
||||
nargs="*",
|
||||
metavar="NAME:VALUE",
|
||||
help="Custom colors (e.g., brand:#3b82f6)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--fonts",
|
||||
nargs="*",
|
||||
metavar="TYPE:FAMILY",
|
||||
help="Custom fonts (e.g., sans:'Inter,system-ui')",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--spacing",
|
||||
nargs="*",
|
||||
metavar="NAME:VALUE",
|
||||
help="Custom spacing (e.g., navbar:4rem)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--breakpoints",
|
||||
nargs="*",
|
||||
metavar="NAME:WIDTH",
|
||||
help="Custom breakpoints (e.g., 3xl:1920px)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--plugins",
|
||||
action="store_true",
|
||||
help="Add recommended plugins",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--validate-only",
|
||||
action="store_true",
|
||||
help="Validate config without writing file",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialize generator
|
||||
generator = TailwindConfigGenerator(
|
||||
typescript=not args.js,
|
||||
framework=args.framework,
|
||||
output_path=args.output,
|
||||
)
|
||||
|
||||
# Add custom colors
|
||||
if args.colors:
|
||||
colors = {}
|
||||
for color_spec in args.colors:
|
||||
try:
|
||||
name, value = color_spec.split(":", 1)
|
||||
colors[name] = value
|
||||
except ValueError:
|
||||
print(f"Invalid color spec: {color_spec}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
generator.add_colors(colors)
|
||||
|
||||
# Add custom fonts
|
||||
if args.fonts:
|
||||
fonts = {}
|
||||
for font_spec in args.fonts:
|
||||
try:
|
||||
font_type, family = font_spec.split(":", 1)
|
||||
fonts[font_type] = [f.strip().strip("'\"") for f in family.split(",")]
|
||||
except ValueError:
|
||||
print(f"Invalid font spec: {font_spec}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
generator.add_fonts(fonts)
|
||||
|
||||
# Add custom spacing
|
||||
if args.spacing:
|
||||
spacing = {}
|
||||
for spacing_spec in args.spacing:
|
||||
try:
|
||||
name, value = spacing_spec.split(":", 1)
|
||||
spacing[name] = value
|
||||
except ValueError:
|
||||
print(f"Invalid spacing spec: {spacing_spec}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
generator.add_spacing(spacing)
|
||||
|
||||
# Add custom breakpoints
|
||||
if args.breakpoints:
|
||||
breakpoints = {}
|
||||
for bp_spec in args.breakpoints:
|
||||
try:
|
||||
name, width = bp_spec.split(":", 1)
|
||||
breakpoints[name] = width
|
||||
except ValueError:
|
||||
print(f"Invalid breakpoint spec: {bp_spec}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
generator.add_breakpoints(breakpoints)
|
||||
|
||||
# Add recommended plugins
|
||||
if args.plugins:
|
||||
recommended = generator.recommend_plugins()
|
||||
generator.add_plugins(recommended)
|
||||
print(f"Added recommended plugins: {', '.join(recommended)}")
|
||||
print("\nInstall with:")
|
||||
print(f" npm install -D {' '.join(recommended)}")
|
||||
|
||||
# Validate
|
||||
valid, message = generator.validate_config()
|
||||
if not valid:
|
||||
print(f"Validation failed: {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if message.startswith("Warning"):
|
||||
print(message)
|
||||
|
||||
# Validate only mode
|
||||
if args.validate_only:
|
||||
print("Configuration valid")
|
||||
print("\nGenerated config:")
|
||||
print(generator.generate_config_string())
|
||||
sys.exit(0)
|
||||
|
||||
# Write config
|
||||
success, message = generator.write_config()
|
||||
print(message)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
skills/ui-styling/scripts/tests/coverage-ui.json
Normal file
1
skills/ui-styling/scripts/tests/coverage-ui.json
Normal file
File diff suppressed because one or more lines are too long
3
skills/ui-styling/scripts/tests/requirements.txt
Normal file
3
skills/ui-styling/scripts/tests/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pytest>=7.4.0
|
||||
pytest-cov>=4.1.0
|
||||
pytest-mock>=3.11.1
|
||||
266
skills/ui-styling/scripts/tests/test_shadcn_add.py
Normal file
266
skills/ui-styling/scripts/tests/test_shadcn_add.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""Tests for shadcn_add.py"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
# Add parent directory to path for imports
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from shadcn_add import ShadcnInstaller
|
||||
|
||||
|
||||
class TestShadcnInstaller:
|
||||
"""Test ShadcnInstaller class."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_project(self, tmp_path):
|
||||
"""Create temporary project structure."""
|
||||
project_root = tmp_path / "test-project"
|
||||
project_root.mkdir()
|
||||
|
||||
# Create components.json
|
||||
components_json = project_root / "components.json"
|
||||
components_json.write_text(
|
||||
json.dumps({
|
||||
"style": "new-york",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
# Create components directory
|
||||
ui_dir = project_root / "components" / "ui"
|
||||
ui_dir.mkdir(parents=True)
|
||||
|
||||
return project_root
|
||||
|
||||
def test_init_default_project_root(self):
|
||||
"""Test initialization with default project root."""
|
||||
installer = ShadcnInstaller()
|
||||
assert installer.project_root == Path.cwd()
|
||||
assert installer.dry_run is False
|
||||
|
||||
def test_init_custom_project_root(self, tmp_path):
|
||||
"""Test initialization with custom project root."""
|
||||
installer = ShadcnInstaller(project_root=tmp_path)
|
||||
assert installer.project_root == tmp_path
|
||||
|
||||
def test_init_dry_run(self):
|
||||
"""Test initialization with dry run mode."""
|
||||
installer = ShadcnInstaller(dry_run=True)
|
||||
assert installer.dry_run is True
|
||||
|
||||
def test_check_shadcn_config_exists(self, temp_project):
|
||||
"""Test checking for existing shadcn config."""
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
assert installer.check_shadcn_config() is True
|
||||
|
||||
def test_check_shadcn_config_not_exists(self, tmp_path):
|
||||
"""Test checking for non-existent shadcn config."""
|
||||
installer = ShadcnInstaller(project_root=tmp_path)
|
||||
assert installer.check_shadcn_config() is False
|
||||
|
||||
def test_get_installed_components_empty(self, temp_project):
|
||||
"""Test getting installed components when none exist."""
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
installed = installer.get_installed_components()
|
||||
assert installed == []
|
||||
|
||||
def test_get_installed_components_with_files(self, temp_project):
|
||||
"""Test getting installed components when files exist."""
|
||||
ui_dir = temp_project / "components" / "ui"
|
||||
|
||||
# Create component files
|
||||
(ui_dir / "button.tsx").write_text("export const Button = () => {}")
|
||||
(ui_dir / "card.tsx").write_text("export const Card = () => {}")
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
installed = installer.get_installed_components()
|
||||
|
||||
assert sorted(installed) == ["button", "card"]
|
||||
|
||||
def test_get_installed_components_no_config(self, tmp_path):
|
||||
"""Test getting installed components without config."""
|
||||
installer = ShadcnInstaller(project_root=tmp_path)
|
||||
installed = installer.get_installed_components()
|
||||
assert installed == []
|
||||
|
||||
def test_add_components_no_components(self, temp_project):
|
||||
"""Test adding components with empty list."""
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.add_components([])
|
||||
|
||||
assert success is False
|
||||
assert "No components specified" in message
|
||||
|
||||
def test_add_components_no_config(self, tmp_path):
|
||||
"""Test adding components without shadcn config."""
|
||||
installer = ShadcnInstaller(project_root=tmp_path)
|
||||
success, message = installer.add_components(["button"])
|
||||
|
||||
assert success is False
|
||||
assert "not initialized" in message
|
||||
|
||||
def test_add_components_already_installed(self, temp_project):
|
||||
"""Test adding components that are already installed."""
|
||||
ui_dir = temp_project / "components" / "ui"
|
||||
(ui_dir / "button.tsx").write_text("export const Button = () => {}")
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.add_components(["button"])
|
||||
|
||||
assert success is False
|
||||
assert "already installed" in message
|
||||
assert "button" in message
|
||||
|
||||
def test_add_components_with_overwrite(self, temp_project):
|
||||
"""Test adding components with overwrite flag."""
|
||||
ui_dir = temp_project / "components" / "ui"
|
||||
(ui_dir / "button.tsx").write_text("export const Button = () => {}")
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(
|
||||
stdout="Component added successfully",
|
||||
returncode=0
|
||||
)
|
||||
|
||||
success, message = installer.add_components(["button"], overwrite=True)
|
||||
|
||||
assert success is True
|
||||
assert "Successfully added" in message
|
||||
mock_run.assert_called_once()
|
||||
|
||||
# Verify --overwrite flag was passed
|
||||
call_args = mock_run.call_args[0][0]
|
||||
assert "--overwrite" in call_args
|
||||
|
||||
def test_add_components_dry_run(self, temp_project):
|
||||
"""Test adding components in dry run mode."""
|
||||
installer = ShadcnInstaller(project_root=temp_project, dry_run=True)
|
||||
success, message = installer.add_components(["button", "card"])
|
||||
|
||||
assert success is True
|
||||
assert "Would run:" in message
|
||||
assert "button" in message
|
||||
assert "card" in message
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_add_components_success(self, mock_run, temp_project):
|
||||
"""Test successful component addition."""
|
||||
mock_run.return_value = MagicMock(
|
||||
stdout="Components added successfully",
|
||||
stderr="",
|
||||
returncode=0
|
||||
)
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.add_components(["button", "card"])
|
||||
|
||||
assert success is True
|
||||
assert "Successfully added" in message
|
||||
assert "button" in message
|
||||
assert "card" in message
|
||||
|
||||
# Verify correct command was called
|
||||
mock_run.assert_called_once()
|
||||
call_args = mock_run.call_args[0][0]
|
||||
assert call_args[:3] == ["npx", "shadcn@latest", "add"]
|
||||
assert "button" in call_args
|
||||
assert "card" in call_args
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_add_components_subprocess_error(self, mock_run, temp_project):
|
||||
"""Test component addition with subprocess error."""
|
||||
mock_run.side_effect = subprocess.CalledProcessError(
|
||||
1, "cmd", stderr="Error occurred"
|
||||
)
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.add_components(["button"])
|
||||
|
||||
assert success is False
|
||||
assert "Failed to add" in message
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_add_components_npx_not_found(self, mock_run, temp_project):
|
||||
"""Test component addition when npx is not found."""
|
||||
mock_run.side_effect = FileNotFoundError()
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.add_components(["button"])
|
||||
|
||||
assert success is False
|
||||
assert "npx not found" in message
|
||||
|
||||
def test_add_all_components_no_config(self, tmp_path):
|
||||
"""Test adding all components without config."""
|
||||
installer = ShadcnInstaller(project_root=tmp_path)
|
||||
success, message = installer.add_all_components()
|
||||
|
||||
assert success is False
|
||||
assert "not initialized" in message
|
||||
|
||||
def test_add_all_components_dry_run(self, temp_project):
|
||||
"""Test adding all components in dry run mode."""
|
||||
installer = ShadcnInstaller(project_root=temp_project, dry_run=True)
|
||||
success, message = installer.add_all_components()
|
||||
|
||||
assert success is True
|
||||
assert "Would run:" in message
|
||||
assert "--all" in message
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_add_all_components_success(self, mock_run, temp_project):
|
||||
"""Test successful addition of all components."""
|
||||
mock_run.return_value = MagicMock(
|
||||
stdout="All components added",
|
||||
returncode=0
|
||||
)
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.add_all_components()
|
||||
|
||||
assert success is True
|
||||
assert "Successfully added all" in message
|
||||
|
||||
# Verify --all flag was passed
|
||||
call_args = mock_run.call_args[0][0]
|
||||
assert "--all" in call_args
|
||||
|
||||
def test_list_installed_no_config(self, tmp_path):
|
||||
"""Test listing installed components without config."""
|
||||
installer = ShadcnInstaller(project_root=tmp_path)
|
||||
success, message = installer.list_installed()
|
||||
|
||||
assert success is False
|
||||
assert "not initialized" in message
|
||||
|
||||
def test_list_installed_empty(self, temp_project):
|
||||
"""Test listing installed components when none exist."""
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.list_installed()
|
||||
|
||||
assert success is True
|
||||
assert "No components installed" in message
|
||||
|
||||
def test_list_installed_with_components(self, temp_project):
|
||||
"""Test listing installed components when they exist."""
|
||||
ui_dir = temp_project / "components" / "ui"
|
||||
(ui_dir / "button.tsx").write_text("export const Button = () => {}")
|
||||
(ui_dir / "card.tsx").write_text("export const Card = () => {}")
|
||||
|
||||
installer = ShadcnInstaller(project_root=temp_project)
|
||||
success, message = installer.list_installed()
|
||||
|
||||
assert success is True
|
||||
assert "button" in message
|
||||
assert "card" in message
|
||||
336
skills/ui-styling/scripts/tests/test_tailwind_config_gen.py
Normal file
336
skills/ui-styling/scripts/tests/test_tailwind_config_gen.py
Normal file
@@ -0,0 +1,336 @@
|
||||
"""Tests for tailwind_config_gen.py"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
# Add parent directory to path for imports
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from tailwind_config_gen import TailwindConfigGenerator
|
||||
|
||||
|
||||
class TestTailwindConfigGenerator:
|
||||
"""Test TailwindConfigGenerator class."""
|
||||
|
||||
def test_init_default_typescript(self):
|
||||
"""Test initialization with default settings."""
|
||||
generator = TailwindConfigGenerator()
|
||||
assert generator.typescript is True
|
||||
assert generator.framework == "react"
|
||||
|
||||
def test_init_javascript(self):
|
||||
"""Test initialization for JavaScript config."""
|
||||
generator = TailwindConfigGenerator(typescript=False)
|
||||
assert generator.typescript is False
|
||||
|
||||
def test_init_framework(self):
|
||||
"""Test initialization with different frameworks."""
|
||||
for framework in ["react", "vue", "svelte", "nextjs"]:
|
||||
generator = TailwindConfigGenerator(framework=framework)
|
||||
assert generator.framework == framework
|
||||
|
||||
def test_default_output_path_typescript(self):
|
||||
"""Test default output path for TypeScript."""
|
||||
generator = TailwindConfigGenerator(typescript=True)
|
||||
assert generator.output_path.name == "tailwind.config.ts"
|
||||
|
||||
def test_default_output_path_javascript(self):
|
||||
"""Test default output path for JavaScript."""
|
||||
generator = TailwindConfigGenerator(typescript=False)
|
||||
assert generator.output_path.name == "tailwind.config.js"
|
||||
|
||||
def test_custom_output_path(self, tmp_path):
|
||||
"""Test custom output path."""
|
||||
custom_path = tmp_path / "custom-config.ts"
|
||||
generator = TailwindConfigGenerator(output_path=custom_path)
|
||||
assert generator.output_path == custom_path
|
||||
|
||||
def test_base_config_structure(self):
|
||||
"""Test base configuration structure."""
|
||||
generator = TailwindConfigGenerator()
|
||||
config = generator.config
|
||||
|
||||
assert "darkMode" in config
|
||||
assert "content" in config
|
||||
assert "theme" in config
|
||||
assert "plugins" in config
|
||||
assert "extend" in config["theme"]
|
||||
|
||||
def test_default_content_paths_react(self):
|
||||
"""Test default content paths for React."""
|
||||
generator = TailwindConfigGenerator(framework="react")
|
||||
paths = generator.config["content"]
|
||||
|
||||
assert any("src/**/*.{js,jsx,ts,tsx}" in p for p in paths)
|
||||
assert any("index.html" in p for p in paths)
|
||||
|
||||
def test_default_content_paths_nextjs(self):
|
||||
"""Test default content paths for Next.js."""
|
||||
generator = TailwindConfigGenerator(framework="nextjs")
|
||||
paths = generator.config["content"]
|
||||
|
||||
assert any("app/**" in p for p in paths)
|
||||
assert any("pages/**" in p for p in paths)
|
||||
assert any("components/**" in p for p in paths)
|
||||
|
||||
def test_default_content_paths_vue(self):
|
||||
"""Test default content paths for Vue."""
|
||||
generator = TailwindConfigGenerator(framework="vue")
|
||||
paths = generator.config["content"]
|
||||
|
||||
assert any("vue" in p for p in paths)
|
||||
|
||||
def test_add_colors(self):
|
||||
"""Test adding custom colors."""
|
||||
generator = TailwindConfigGenerator()
|
||||
colors = {
|
||||
"brand": "#3b82f6",
|
||||
"accent": "#8b5cf6"
|
||||
}
|
||||
generator.add_colors(colors)
|
||||
|
||||
assert "colors" in generator.config["theme"]["extend"]
|
||||
assert generator.config["theme"]["extend"]["colors"]["brand"] == "#3b82f6"
|
||||
assert generator.config["theme"]["extend"]["colors"]["accent"] == "#8b5cf6"
|
||||
|
||||
def test_add_colors_multiple_times(self):
|
||||
"""Test adding colors multiple times."""
|
||||
generator = TailwindConfigGenerator()
|
||||
|
||||
generator.add_colors({"brand": "#3b82f6"})
|
||||
generator.add_colors({"accent": "#8b5cf6"})
|
||||
|
||||
colors = generator.config["theme"]["extend"]["colors"]
|
||||
assert "brand" in colors
|
||||
assert "accent" in colors
|
||||
|
||||
def test_add_color_palette(self):
|
||||
"""Test adding full color palette."""
|
||||
generator = TailwindConfigGenerator()
|
||||
generator.add_color_palette("brand", "#3b82f6")
|
||||
|
||||
brand = generator.config["theme"]["extend"]["colors"]["brand"]
|
||||
|
||||
assert isinstance(brand, dict)
|
||||
assert "50" in brand
|
||||
assert "500" in brand
|
||||
assert "950" in brand
|
||||
assert "var(--color-brand" in brand["500"]
|
||||
|
||||
def test_add_fonts(self):
|
||||
"""Test adding custom fonts."""
|
||||
generator = TailwindConfigGenerator()
|
||||
fonts = {
|
||||
"sans": ["Inter", "system-ui", "sans-serif"],
|
||||
"display": ["Playfair Display", "serif"]
|
||||
}
|
||||
generator.add_fonts(fonts)
|
||||
|
||||
font_family = generator.config["theme"]["extend"]["fontFamily"]
|
||||
assert font_family["sans"] == ["Inter", "system-ui", "sans-serif"]
|
||||
assert font_family["display"] == ["Playfair Display", "serif"]
|
||||
|
||||
def test_add_spacing(self):
|
||||
"""Test adding custom spacing."""
|
||||
generator = TailwindConfigGenerator()
|
||||
spacing = {
|
||||
"18": "4.5rem",
|
||||
"navbar": "4rem"
|
||||
}
|
||||
generator.add_spacing(spacing)
|
||||
|
||||
spacing_config = generator.config["theme"]["extend"]["spacing"]
|
||||
assert spacing_config["18"] == "4.5rem"
|
||||
assert spacing_config["navbar"] == "4rem"
|
||||
|
||||
def test_add_breakpoints(self):
|
||||
"""Test adding custom breakpoints."""
|
||||
generator = TailwindConfigGenerator()
|
||||
breakpoints = {
|
||||
"3xl": "1920px",
|
||||
"tablet": "768px"
|
||||
}
|
||||
generator.add_breakpoints(breakpoints)
|
||||
|
||||
screens = generator.config["theme"]["extend"]["screens"]
|
||||
assert screens["3xl"] == "1920px"
|
||||
assert screens["tablet"] == "768px"
|
||||
|
||||
def test_add_plugins(self):
|
||||
"""Test adding plugins."""
|
||||
generator = TailwindConfigGenerator()
|
||||
plugins = ["@tailwindcss/typography", "@tailwindcss/forms"]
|
||||
generator.add_plugins(plugins)
|
||||
|
||||
assert "@tailwindcss/typography" in generator.config["plugins"]
|
||||
assert "@tailwindcss/forms" in generator.config["plugins"]
|
||||
|
||||
def test_add_plugins_no_duplicates(self):
|
||||
"""Test that adding same plugin twice doesn't duplicate."""
|
||||
generator = TailwindConfigGenerator()
|
||||
generator.add_plugins(["@tailwindcss/typography"])
|
||||
generator.add_plugins(["@tailwindcss/typography"])
|
||||
|
||||
count = generator.config["plugins"].count("@tailwindcss/typography")
|
||||
assert count == 1
|
||||
|
||||
def test_recommend_plugins(self):
|
||||
"""Test plugin recommendations."""
|
||||
generator = TailwindConfigGenerator()
|
||||
recommendations = generator.recommend_plugins()
|
||||
|
||||
assert isinstance(recommendations, list)
|
||||
assert "tailwindcss-animate" in recommendations
|
||||
|
||||
def test_recommend_plugins_nextjs(self):
|
||||
"""Test plugin recommendations for Next.js."""
|
||||
generator = TailwindConfigGenerator(framework="nextjs")
|
||||
recommendations = generator.recommend_plugins()
|
||||
|
||||
assert "@tailwindcss/typography" in recommendations
|
||||
|
||||
def test_generate_typescript_config(self):
|
||||
"""Test generating TypeScript configuration."""
|
||||
generator = TailwindConfigGenerator(typescript=True)
|
||||
config = generator.generate_config_string()
|
||||
|
||||
assert "import type { Config } from 'tailwindcss'" in config
|
||||
assert "const config: Config" in config
|
||||
assert "export default config" in config
|
||||
|
||||
def test_generate_javascript_config(self):
|
||||
"""Test generating JavaScript configuration."""
|
||||
generator = TailwindConfigGenerator(typescript=False)
|
||||
config = generator.generate_config_string()
|
||||
|
||||
assert "module.exports" in config
|
||||
assert "@type" in config
|
||||
|
||||
def test_generate_config_with_colors(self):
|
||||
"""Test generating config with custom colors."""
|
||||
generator = TailwindConfigGenerator()
|
||||
generator.add_colors({"brand": "#3b82f6"})
|
||||
config = generator.generate_config_string()
|
||||
|
||||
assert "colors" in config
|
||||
assert "brand" in config
|
||||
|
||||
def test_generate_config_with_plugins(self):
|
||||
"""Test generating config with plugins."""
|
||||
generator = TailwindConfigGenerator()
|
||||
generator.add_plugins(["tailwindcss-animate"])
|
||||
config = generator.generate_config_string()
|
||||
|
||||
assert "plugins:" in config
|
||||
assert "require('tailwindcss-animate')" in config
|
||||
|
||||
def test_validate_config_valid(self):
|
||||
"""Test validating valid configuration."""
|
||||
generator = TailwindConfigGenerator()
|
||||
valid, message = generator.validate_config()
|
||||
|
||||
assert valid is True
|
||||
|
||||
def test_validate_config_no_content(self):
|
||||
"""Test validating config with no content paths."""
|
||||
generator = TailwindConfigGenerator()
|
||||
generator.config["content"] = []
|
||||
|
||||
valid, message = generator.validate_config()
|
||||
|
||||
assert valid is False
|
||||
assert "No content paths" in message
|
||||
|
||||
def test_validate_config_empty_theme(self):
|
||||
"""Test validating config with empty theme extensions."""
|
||||
generator = TailwindConfigGenerator()
|
||||
# Default has empty theme.extend
|
||||
|
||||
valid, message = generator.validate_config()
|
||||
|
||||
assert valid is True
|
||||
assert "Warning" in message
|
||||
|
||||
def test_write_config(self, tmp_path):
|
||||
"""Test writing configuration to file."""
|
||||
output_path = tmp_path / "tailwind.config.ts"
|
||||
generator = TailwindConfigGenerator(output_path=output_path)
|
||||
|
||||
success, message = generator.write_config()
|
||||
|
||||
assert success is True
|
||||
assert output_path.exists()
|
||||
assert "written to" in message
|
||||
|
||||
def test_write_config_creates_content(self, tmp_path):
|
||||
"""Test that written config contains expected content."""
|
||||
output_path = tmp_path / "tailwind.config.ts"
|
||||
generator = TailwindConfigGenerator(output_path=output_path)
|
||||
generator.add_colors({"brand": "#3b82f6"})
|
||||
|
||||
generator.write_config()
|
||||
|
||||
content = output_path.read_text()
|
||||
assert "import type { Config }" in content
|
||||
assert "brand" in content
|
||||
|
||||
def test_write_config_invalid_path(self):
|
||||
"""Test writing config to invalid path."""
|
||||
generator = TailwindConfigGenerator(output_path=Path("/invalid/path/config.ts"))
|
||||
|
||||
success, message = generator.write_config()
|
||||
|
||||
assert success is False
|
||||
assert "Failed to write" in message
|
||||
|
||||
def test_full_configuration_typescript(self, tmp_path):
|
||||
"""Test generating complete TypeScript configuration."""
|
||||
output_path = tmp_path / "tailwind.config.ts"
|
||||
generator = TailwindConfigGenerator(
|
||||
typescript=True,
|
||||
framework="nextjs",
|
||||
output_path=output_path
|
||||
)
|
||||
|
||||
# Add various customizations
|
||||
generator.add_colors({"brand": "#3b82f6", "accent": "#8b5cf6"})
|
||||
generator.add_fonts({"sans": ["Inter", "sans-serif"]})
|
||||
generator.add_spacing({"navbar": "4rem"})
|
||||
generator.add_breakpoints({"3xl": "1920px"})
|
||||
generator.add_plugins(["tailwindcss-animate"])
|
||||
|
||||
success, _ = generator.write_config()
|
||||
assert success is True
|
||||
|
||||
content = output_path.read_text()
|
||||
|
||||
# Verify all customizations are present
|
||||
assert "brand" in content
|
||||
assert "accent" in content
|
||||
assert "Inter" in content
|
||||
assert "navbar" in content
|
||||
assert "3xl" in content
|
||||
assert "tailwindcss-animate" in content
|
||||
|
||||
def test_full_configuration_javascript(self, tmp_path):
|
||||
"""Test generating complete JavaScript configuration."""
|
||||
output_path = tmp_path / "tailwind.config.js"
|
||||
generator = TailwindConfigGenerator(
|
||||
typescript=False,
|
||||
framework="react",
|
||||
output_path=output_path
|
||||
)
|
||||
|
||||
generator.add_colors({"primary": "#3b82f6"})
|
||||
generator.add_plugins(["@tailwindcss/forms"])
|
||||
|
||||
success, _ = generator.write_config()
|
||||
assert success is True
|
||||
|
||||
content = output_path.read_text()
|
||||
|
||||
assert "module.exports" in content
|
||||
assert "primary" in content
|
||||
assert "@tailwindcss/forms" in content
|
||||
Reference in New Issue
Block a user