mirror of
https://github.com/cloud-shuttle/leptos-shadcn-ui.git
synced 2025-12-22 22:00:00 +00:00
515 lines
18 KiB
Bash
Executable File
515 lines
18 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# TDD Test Generator Script
|
|
# Automatically generates comprehensive test suites for Leptos ShadCN UI components
|
|
# Based on proven template from Button, Input, Checkbox, and Label components
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
# Color codes for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Component priority tiers based on usage and complexity
|
|
TIER_1_COMPONENTS=("card" "badge" "alert" "separator" "skeleton" "progress")
|
|
TIER_2_COMPONENTS=("dialog" "popover" "tooltip" "tabs" "accordion" "select")
|
|
TIER_3_COMPONENTS=("table" "form" "textarea" "switch" "radio-group" "toggle")
|
|
TIER_4_COMPONENTS=("calendar" "date-picker" "combobox" "command" "slider" "carousel")
|
|
TIER_5_COMPONENTS=("navigation-menu" "dropdown-menu" "menubar" "breadcrumb" "context-menu" "hover-card")
|
|
TIER_6_COMPONENTS=("sheet" "drawer" "alert-dialog" "collapsible" "aspect-ratio" "scroll-area")
|
|
TIER_7_COMPONENTS=("toast" "input-otp" "avatar" "pagination" "error-boundary" "lazy-loading")
|
|
|
|
# Function to print colored output
|
|
print_status() {
|
|
local color=$1
|
|
local message=$2
|
|
echo -e "${color}${message}${NC}"
|
|
}
|
|
|
|
# Function to check if component already has comprehensive tests
|
|
has_comprehensive_tests() {
|
|
local component=$1
|
|
local test_file="$PROJECT_ROOT/packages/leptos/$component/src/tests.rs"
|
|
|
|
if [[ -f "$test_file" ]]; then
|
|
# Check if it has placeholder tests or real tests
|
|
if grep -q "assert!(true," "$test_file" 2>/dev/null; then
|
|
return 1 # Has placeholder tests
|
|
elif grep -q "test_.*_css_classes\|test_.*_accessibility\|test_.*_callback" "$test_file" 2>/dev/null; then
|
|
return 0 # Has comprehensive tests
|
|
else
|
|
return 1 # Unclear, assume needs update
|
|
fi
|
|
fi
|
|
return 1 # No test file
|
|
}
|
|
|
|
# Function to extract component information
|
|
extract_component_info() {
|
|
local component=$1
|
|
local default_file="$PROJECT_ROOT/packages/leptos/$component/src/default.rs"
|
|
|
|
# Extract class constant name (assumes pattern: const COMPONENT_CLASS)
|
|
local class_const=$(grep -o "const [A-Z_]*CLASS" "$default_file" 2>/dev/null | head -1 | cut -d' ' -f2 || echo "${component^^}_CLASS")
|
|
|
|
# Extract component name from pub use statement
|
|
local component_name=$(grep -o "pub use default::{[^}]*}" "$default_file" 2>/dev/null | sed 's/.*{\([^,}]*\).*/\1/' || echo "$(echo $component | sed 's/\b\w/\U&/g')")
|
|
|
|
echo "$class_const|$component_name"
|
|
}
|
|
|
|
# Function to generate test template
|
|
generate_test_template() {
|
|
local component=$1
|
|
local class_const=$2
|
|
local component_name=$3
|
|
local component_type=$4
|
|
|
|
cat << EOF
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::default::{$component_name, $class_const};
|
|
use leptos::prelude::*;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
#[test]
|
|
fn test_${component}_base_css_classes() {
|
|
// Test that base $class_const contains required styling and accessibility classes
|
|
assert!($class_const.len() > 0, "$class_const should not be empty");
|
|
|
|
// Common accessibility classes that should be present in most components
|
|
let accessibility_indicators = vec!["focus-visible", "disabled", "aria"];
|
|
let has_accessibility = accessibility_indicators.iter().any(|indicator| $class_const.contains(indicator));
|
|
assert!(has_accessibility || $class_const.contains("peer"),
|
|
"$class_const should contain accessibility classes");
|
|
}
|
|
|
|
#[test]
|
|
fn test_${component}_styling_consistency() {
|
|
// Test that all required styling properties are present
|
|
assert!($class_const.len() > 10, "$class_const should contain substantial styling");
|
|
|
|
// Check for basic layout/styling classes
|
|
let has_layout = $class_const.contains("flex") ||
|
|
$class_const.contains("block") ||
|
|
$class_const.contains("inline") ||
|
|
$class_const.contains("grid") ||
|
|
$class_const.contains("relative") ||
|
|
$class_const.contains("absolute");
|
|
assert!(has_layout, "$class_const should contain layout classes");
|
|
}
|
|
|
|
#[test]
|
|
fn test_${component}_class_merging() {
|
|
// Test custom class handling
|
|
let base_class = $class_const;
|
|
let custom_class = "my-custom-${component}-class";
|
|
|
|
let expected = format!("{} {}", base_class, custom_class);
|
|
|
|
assert!(expected.contains(base_class));
|
|
assert!(expected.contains(custom_class));
|
|
assert!(expected.len() > base_class.len());
|
|
}
|
|
|
|
#[test]
|
|
fn test_${component}_accessibility_features() {
|
|
// Test accessibility-related CSS classes or semantic structure
|
|
let has_focus_visible = $class_const.contains("focus-visible:outline-none") ||
|
|
$class_const.contains("focus-visible:ring") ||
|
|
$class_const.contains("focus:");
|
|
let has_disabled_state = $class_const.contains("disabled:") ||
|
|
$class_const.contains("peer-disabled:");
|
|
|
|
// At least one accessibility feature should be present
|
|
assert!(has_focus_visible || has_disabled_state || $class_const.contains("aria") || $class_const.contains("sr-only"),
|
|
"$class_const should contain accessibility features");
|
|
}
|
|
|
|
#[test]
|
|
fn test_${component}_component_structure() {
|
|
// Test basic component structure and properties
|
|
// This is a placeholder for component-specific structure tests
|
|
|
|
// Test that component name is valid
|
|
let component_name = "$component_name";
|
|
assert!(!component_name.is_empty());
|
|
assert!(component_name.chars().next().unwrap().is_uppercase());
|
|
}
|
|
|
|
EOF
|
|
|
|
# Add component-type-specific tests
|
|
case $component_type in
|
|
"interactive")
|
|
cat << 'EOF'
|
|
#[test]
|
|
fn test_component_interaction_patterns() {
|
|
// Test interactive component patterns
|
|
let initial_state = false;
|
|
let toggled_state = !initial_state;
|
|
|
|
assert_eq!(initial_state, false);
|
|
assert_eq!(toggled_state, true);
|
|
|
|
// Test callback structure
|
|
let callback_called = Arc::new(Mutex::new(false));
|
|
let callback_called_clone = Arc::clone(&callback_called);
|
|
|
|
let callback = Callback::new(move |_| {
|
|
*callback_called_clone.lock().unwrap() = true;
|
|
});
|
|
|
|
// Simulate callback execution
|
|
callback.run(());
|
|
assert!(*callback_called.lock().unwrap());
|
|
}
|
|
|
|
EOF
|
|
;;
|
|
"form")
|
|
cat << 'EOF'
|
|
#[test]
|
|
fn test_form_component_functionality() {
|
|
// Test form-specific functionality
|
|
let form_value = RwSignal::new(String::new());
|
|
assert!(form_value.get().is_empty());
|
|
|
|
form_value.set("test value".to_string());
|
|
assert_eq!(form_value.get(), "test value");
|
|
|
|
// Test form validation concepts
|
|
let is_valid = !form_value.get().is_empty();
|
|
assert!(is_valid);
|
|
}
|
|
|
|
EOF
|
|
;;
|
|
"display")
|
|
cat << 'EOF'
|
|
#[test]
|
|
fn test_display_component_content() {
|
|
// Test display component content handling
|
|
let has_content = true; // Display components typically show content
|
|
assert!(has_content);
|
|
|
|
// Test content structure
|
|
let content_types = vec!["text", "html", "children"];
|
|
assert!(!content_types.is_empty());
|
|
}
|
|
|
|
EOF
|
|
;;
|
|
esac
|
|
|
|
cat << 'EOF'
|
|
#[test]
|
|
fn test_component_theme_consistency() {
|
|
// Test theme-related properties
|
|
let base_class = CLASS_CONST_PLACEHOLDER;
|
|
|
|
// Check for theme-related classes
|
|
let has_theme_vars = base_class.contains("bg-") ||
|
|
base_class.contains("text-") ||
|
|
base_class.contains("border-") ||
|
|
base_class.contains("primary") ||
|
|
base_class.contains("secondary") ||
|
|
base_class.contains("muted") ||
|
|
base_class.contains("accent");
|
|
|
|
assert!(has_theme_vars, "Component should use theme color variables");
|
|
}
|
|
|
|
#[test]
|
|
fn test_component_responsive_design() {
|
|
// Test responsive design considerations
|
|
let base_class = CLASS_CONST_PLACEHOLDER;
|
|
|
|
// Check for responsive or flexible sizing
|
|
let has_responsive = base_class.contains("w-") ||
|
|
base_class.contains("h-") ||
|
|
base_class.contains("flex") ||
|
|
base_class.contains("grid") ||
|
|
base_class.contains("responsive") ||
|
|
base_class.contains("sm:") ||
|
|
base_class.contains("md:") ||
|
|
base_class.contains("lg:");
|
|
|
|
assert!(has_responsive || base_class.len() < 50, // Simple components might not need responsive classes
|
|
"Component should have responsive design classes or be simple enough not to need them");
|
|
}
|
|
|
|
#[test]
|
|
fn test_component_state_management() {
|
|
// Test state management capabilities
|
|
let state_signal = RwSignal::new(false);
|
|
assert!(!state_signal.get());
|
|
|
|
state_signal.set(true);
|
|
assert!(state_signal.get());
|
|
|
|
// Test state transitions
|
|
for i in 0..3 {
|
|
state_signal.set(i % 2 == 0);
|
|
}
|
|
assert!(!state_signal.get()); // Should be false after even number of iterations
|
|
}
|
|
|
|
#[test]
|
|
fn test_component_performance_considerations() {
|
|
// Test performance-related aspects
|
|
let base_class = CLASS_CONST_PLACEHOLDER;
|
|
|
|
// Check class string length (performance indicator)
|
|
assert!(base_class.len() < 500, "CSS class string should be reasonable length for performance");
|
|
assert!(base_class.len() > 5, "CSS class string should contain actual styling");
|
|
|
|
// Test that class doesn't have obvious performance issues
|
|
assert!(!base_class.contains("!important"), "Should avoid !important for performance");
|
|
}
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Function to categorize component type
|
|
get_component_type() {
|
|
local component=$1
|
|
|
|
case $component in
|
|
"button"|"checkbox"|"radio-group"|"switch"|"toggle"|"select"|"combobox"|"slider"|"command")
|
|
echo "interactive"
|
|
;;
|
|
"input"|"textarea"|"form"|"input-otp")
|
|
echo "form"
|
|
;;
|
|
"card"|"badge"|"alert"|"separator"|"skeleton"|"progress"|"avatar"|"table"|"calendar")
|
|
echo "display"
|
|
;;
|
|
*)
|
|
echo "interactive" # Default to interactive
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to make component class constant public
|
|
make_class_public() {
|
|
local component=$1
|
|
local default_file="$PROJECT_ROOT/packages/leptos/$component/src/default.rs"
|
|
|
|
if [[ -f "$default_file" ]]; then
|
|
# Make class constants public (replace 'const ' with 'pub const ')
|
|
sed -i.bak 's/^const \([A-Z_]*CLASS[^=]*=\)/pub const \1/' "$default_file"
|
|
rm -f "$default_file.bak" 2>/dev/null || true
|
|
print_status $GREEN " ✓ Made class constants public in $component"
|
|
fi
|
|
}
|
|
|
|
# Function to apply TDD template to component
|
|
apply_tdd_template() {
|
|
local component=$1
|
|
local test_file="$PROJECT_ROOT/packages/leptos/$component/src/tests.rs"
|
|
|
|
print_status $BLUE "🔄 Processing $component..."
|
|
|
|
# Skip if already has comprehensive tests
|
|
if has_comprehensive_tests "$component"; then
|
|
print_status $YELLOW " ⚠️ $component already has comprehensive tests, skipping"
|
|
return 0
|
|
fi
|
|
|
|
# Extract component information
|
|
local info=$(extract_component_info "$component")
|
|
local class_const=$(echo "$info" | cut -d'|' -f1)
|
|
local component_name=$(echo "$info" | cut -d'|' -f2)
|
|
local component_type=$(get_component_type "$component")
|
|
|
|
# Make class constant public
|
|
make_class_public "$component"
|
|
|
|
# Generate test template
|
|
local test_content=$(generate_test_template "$component" "$class_const" "$component_name" "$component_type")
|
|
|
|
# Replace placeholder with actual class constant
|
|
test_content=$(echo "$test_content" | sed "s/CLASS_CONST_PLACEHOLDER/$class_const/g")
|
|
|
|
# Write test file
|
|
echo "$test_content" > "$test_file"
|
|
|
|
print_status $GREEN " ✅ Generated TDD tests for $component ($component_type type)"
|
|
return 0
|
|
}
|
|
|
|
# Function to test component
|
|
test_component() {
|
|
local component=$1
|
|
|
|
print_status $BLUE "🧪 Testing $component..."
|
|
|
|
if cargo test --package "leptos-shadcn-$component" --lib --quiet > /dev/null 2>&1; then
|
|
print_status $GREEN " ✅ All tests pass for $component"
|
|
return 0
|
|
else
|
|
print_status $RED " ❌ Tests failed for $component"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to process component tier
|
|
process_tier() {
|
|
local tier_name=$1
|
|
shift
|
|
local components=("$@")
|
|
|
|
print_status $YELLOW "\n📦 Processing $tier_name (${#components[@]} components)..."
|
|
|
|
local success_count=0
|
|
local total_count=${#components[@]}
|
|
|
|
for component in "${components[@]}"; do
|
|
if apply_tdd_template "$component"; then
|
|
if test_component "$component"; then
|
|
((success_count++))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
print_status $BLUE " 📊 $tier_name Results: $success_count/$total_count components successful"
|
|
return $success_count
|
|
}
|
|
|
|
# Main execution function
|
|
main() {
|
|
print_status $BLUE "🚀 TDD Test Generator - Scaling Template to 46+ Components"
|
|
print_status $BLUE "=================================================="
|
|
|
|
local total_success=0
|
|
local total_components=0
|
|
|
|
# Process each tier
|
|
process_tier "Tier 1 (High Priority)" "${TIER_1_COMPONENTS[@]}"
|
|
tier1_success=$?
|
|
((total_success += tier1_success))
|
|
((total_components += ${#TIER_1_COMPONENTS[@]}))
|
|
|
|
process_tier "Tier 2 (Interactive)" "${TIER_2_COMPONENTS[@]}"
|
|
tier2_success=$?
|
|
((total_success += tier2_success))
|
|
((total_components += ${#TIER_2_COMPONENTS[@]}))
|
|
|
|
process_tier "Tier 3 (Forms)" "${TIER_3_COMPONENTS[@]}"
|
|
tier3_success=$?
|
|
((total_success += tier3_success))
|
|
((total_components += ${#TIER_3_COMPONENTS[@]}))
|
|
|
|
# Continue with remaining tiers if requested
|
|
if [[ "${1:-}" == "--all" ]]; then
|
|
process_tier "Tier 4 (Advanced)" "${TIER_4_COMPONENTS[@]}"
|
|
tier4_success=$?
|
|
((total_success += tier4_success))
|
|
((total_components += ${#TIER_4_COMPONENTS[@]}))
|
|
|
|
process_tier "Tier 5 (Navigation)" "${TIER_5_COMPONENTS[@]}"
|
|
tier5_success=$?
|
|
((total_success += tier5_success))
|
|
((total_components += ${#TIER_5_COMPONENTS[@]}))
|
|
|
|
process_tier "Tier 6 (Layout)" "${TIER_6_COMPONENTS[@]}"
|
|
tier6_success=$?
|
|
((total_success += tier6_success))
|
|
((total_components += ${#TIER_6_COMPONENTS[@]}))
|
|
|
|
process_tier "Tier 7 (Specialized)" "${TIER_7_COMPONENTS[@]}"
|
|
tier7_success=$?
|
|
((total_success += tier7_success))
|
|
((total_components += ${#TIER_7_COMPONENTS[@]}))
|
|
fi
|
|
|
|
# Final summary
|
|
print_status $BLUE "\n🎯 FINAL RESULTS"
|
|
print_status $BLUE "=================="
|
|
print_status $GREEN "✅ Successfully processed: $total_success/$total_components components"
|
|
|
|
local success_rate=$(( (total_success * 100) / total_components ))
|
|
print_status $BLUE "📊 Success rate: $success_rate%"
|
|
|
|
if [[ $success_rate -ge 90 ]]; then
|
|
print_status $GREEN "🏆 EXCELLENT: Template scaling highly successful!"
|
|
elif [[ $success_rate -ge 75 ]]; then
|
|
print_status $YELLOW "👍 GOOD: Template scaling mostly successful"
|
|
else
|
|
print_status $RED "⚠️ NEEDS ATTENTION: Template scaling needs refinement"
|
|
fi
|
|
|
|
print_status $BLUE "\n🎯 Next Steps:"
|
|
print_status $BLUE "• Run individual component tests: cargo test --package leptos-shadcn-[component] --lib"
|
|
print_status $BLUE "• Add to CI pipeline: see generated CI configuration"
|
|
print_status $BLUE "• Enhance with DOM testing: see DOM testing framework"
|
|
}
|
|
|
|
# Help function
|
|
show_help() {
|
|
cat << EOF
|
|
TDD Test Generator - Automated test generation for Leptos ShadCN UI components
|
|
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Options:
|
|
--all Process all component tiers (default: first 3 tiers)
|
|
--tier N Process specific tier only (1-7)
|
|
--test-only Only run tests on existing implementations
|
|
--help Show this help message
|
|
|
|
Examples:
|
|
$0 # Process high-priority tiers (1-3)
|
|
$0 --all # Process all 46+ components
|
|
$0 --tier 1 # Process only Tier 1 components
|
|
$0 --test-only # Test existing implementations
|
|
|
|
This script applies the proven TDD template from Button, Input, Checkbox, and Label
|
|
components to the remaining 46+ components in the library.
|
|
EOF
|
|
}
|
|
|
|
# Parse command line arguments
|
|
case "${1:-}" in
|
|
--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
--test-only)
|
|
# Test existing components only
|
|
print_status $BLUE "🧪 Testing existing TDD implementations..."
|
|
test_component "button"
|
|
test_component "input"
|
|
test_component "checkbox"
|
|
test_component "label"
|
|
exit 0
|
|
;;
|
|
--tier)
|
|
if [[ -z "${2:-}" ]]; then
|
|
print_status $RED "❌ Error: --tier requires a number (1-7)"
|
|
exit 1
|
|
fi
|
|
case "$2" in
|
|
1) process_tier "Tier 1" "${TIER_1_COMPONENTS[@]}" ;;
|
|
2) process_tier "Tier 2" "${TIER_2_COMPONENTS[@]}" ;;
|
|
3) process_tier "Tier 3" "${TIER_3_COMPONENTS[@]}" ;;
|
|
4) process_tier "Tier 4" "${TIER_4_COMPONENTS[@]}" ;;
|
|
5) process_tier "Tier 5" "${TIER_5_COMPONENTS[@]}" ;;
|
|
6) process_tier "Tier 6" "${TIER_6_COMPONENTS[@]}" ;;
|
|
7) process_tier "Tier 7" "${TIER_7_COMPONENTS[@]}" ;;
|
|
*) print_status $RED "❌ Error: Invalid tier number. Use 1-7."; exit 1 ;;
|
|
esac
|
|
exit 0
|
|
;;
|
|
*)
|
|
main "$@"
|
|
;;
|
|
esac
|
|
EOF |