customPropertyParser = new CustomPropertyParser(); $this->classNameParser = new ClassNameParser(); $this->cssParser = new CssParser($this->customPropertyParser, $this->classNameParser); $this->colorAnalyzer = new ColorAnalyzer(); $this->tokenAnalyzer = new TokenAnalyzer(); $this->componentDetector = new ComponentDetector(); $this->conventionChecker = new ConventionChecker(); $this->analyzer = new DesignSystemAnalyzer( $this->cssParser, $this->colorAnalyzer, $this->tokenAnalyzer, $this->componentDetector, $this->conventionChecker ); }); it('analyzes complete design system from CSS files', function () { $cssFiles = [ '/test/tokens.css' => ' :root { --color-primary-500: #3b82f6; --color-secondary-500: #6b7280; --spacing-md: 1rem; --font-size-base: 16px; --border-radius-md: 0.375rem; } ', '/test/components.css' => ' .button { padding: var(--spacing-md); border-radius: var(--border-radius-md); background-color: var(--color-primary-500); } .button--secondary { background-color: var(--color-secondary-500); } .card { border-radius: var(--border-radius-md); padding: calc(var(--spacing-md) * 2); } .card__header { margin-bottom: var(--spacing-md); } ', ]; $analysis = $this->analyzer->analyze($cssFiles); expect($analysis->designTokens)->not->toBeEmpty(); expect($analysis->components)->not->toBeEmpty(); expect($analysis->colorPalette)->not->toBeEmpty(); expect($analysis->maturityScore)->toBeGreaterThan(0); expect($analysis->recommendations)->not->toBeEmpty(); // Check token analysis expect($analysis->tokenAnalysis['categories'])->toHaveKey('color'); expect($analysis->tokenAnalysis['categories'])->toHaveKey('spacing'); expect($analysis->tokenAnalysis['categories'])->toHaveKey('typography'); // Check component analysis expect($analysis->componentAnalysis['bem_components'])->toHaveCount(2); // button, card expect($analysis->componentAnalysis['bem_components'][0]['block'])->toBe('button'); expect($analysis->componentAnalysis['bem_components'][1]['block'])->toBe('card'); // Check color analysis expect($analysis->colorAnalysis['total_colors'])->toBe(2); expect($analysis->colorAnalysis['color_scheme'])->toBeIn(['light', 'dark', 'mixed']); }); it('calculates design system maturity score', function () { // Mature design system $matureSystem = [ '/test/tokens.css' => ' :root { /* Comprehensive color scale */ --color-primary-100: #dbeafe; --color-primary-500: #3b82f6; --color-primary-900: #1e3a8a; /* Consistent spacing scale */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --spacing-xl: 2rem; /* Typography scale */ --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; } ', '/test/components.css' => ' .btn { /* BEM naming */ } .btn--primary { } .btn--secondary { } .btn__icon { } .card { } .card__header { } .card__body { } .card__footer { } .form { } .form__group { } .form__label { } .form__input { } ', ]; $matureAnalysis = $this->analyzer->analyze($matureSystem); // Basic design system $basicSystem = [ '/test/basic.css' => ' :root { --main-color: red; --bg-color: white; } .redButton { color: red; } .blueDiv { background: blue; } ', ]; $basicAnalysis = $this->analyzer->analyze($basicSystem); expect($matureAnalysis->maturityScore)->toBeGreaterThan($basicAnalysis->maturityScore); expect($matureAnalysis->maturityLevel)->toBeIn(['Developing', 'Established', 'Mature']); expect($basicAnalysis->maturityLevel)->toBeIn(['Basic', 'Emerging']); }); it('identifies design system gaps and improvements', function () { $incompleteSystem = [ '/test/gaps.css' => ' :root { --primary: #3b82f6; /* Missing scale */ --big-space: 2rem; /* Inconsistent naming */ --tiny: 2px; /* Non-standard value */ } .redButton { color: red; } /* Presentational naming */ .bigBox { size: large; } /* Presentational naming */ .card_header { } /* Wrong BEM separator */ .NAVIGATION { } /* Wrong case */ ', ]; $analysis = $this->analyzer->analyze($incompleteSystem); expect($analysis->gaps)->not->toBeEmpty(); expect($analysis->recommendations)->not->toBeEmpty(); // Check for specific gap types $gapTypes = array_column($analysis->gaps, 'type'); expect($gapTypes)->toContain('incomplete_color_scale'); expect($gapTypes)->toContain('inconsistent_naming'); expect($gapTypes)->toContain('non_standard_values'); // Check recommendations $recommendationTexts = array_column($analysis->recommendations, 'text'); expect($recommendationTexts)->toContainStrings([ 'naming convention', 'color scale', 'BEM', ]); }); it('analyzes design system consistency', function () { $consistentSystem = [ '/test/consistent.css' => ' :root { --color-primary-500: #3b82f6; --color-secondary-500: #6b7280; --spacing-sm: 0.5rem; --spacing-md: 1rem; } .button { } .button--primary { } .button--secondary { } .card { } .card__header { } .card__body { } ', ]; $inconsistentSystem = [ '/test/inconsistent.css' => ' :root { --primaryColor: #3b82f6; /* camelCase */ --secondary_color: #6b7280; /* snake_case */ --SPACING_SM: 0.5rem; /* UPPER_CASE */ } .btn { } /* Inconsistent with button */ .button { } /* Mixed naming */ .card_header { } /* Wrong separator */ .CardBody { } /* PascalCase */ ', ]; $consistentAnalysis = $this->analyzer->analyze($consistentSystem); $inconsistentAnalysis = $this->analyzer->analyze($inconsistentSystem); expect($consistentAnalysis->consistencyScore)->toBeGreaterThan($inconsistentAnalysis->consistencyScore); expect($consistentAnalysis->consistencyScore)->toBeGreaterThan(0.8); expect($inconsistentAnalysis->consistencyScore)->toBeLessThan(0.5); expect($inconsistentAnalysis->conventionViolations)->not->toBeEmpty(); }); it('generates comprehensive design system report', function () { $system = [ '/test/comprehensive.css' => ' :root { --color-primary-500: #3b82f6; --color-secondary-500: #6b7280; --spacing-md: 1rem; --font-size-base: 16px; --border-radius-md: 0.375rem; } .button { padding: var(--spacing-md); font-size: var(--font-size-base); border-radius: var(--border-radius-md); background-color: var(--color-primary-500); } .button--secondary { background-color: var(--color-secondary-500); } .card { border-radius: var(--border-radius-md); padding: var(--spacing-md); } ', ]; $analysis = $this->analyzer->analyze($system); // Verify all sections are present expect($analysis->overview)->toHaveKeys(['total_tokens', 'total_components', 'maturity_level']); expect($analysis->tokenAnalysis)->toHaveKeys(['categories', 'naming_patterns', 'usage_analysis']); expect($analysis->componentAnalysis)->toHaveKeys(['bem_components', 'utility_patterns', 'complexity_analysis']); expect($analysis->colorAnalysis)->toHaveKeys(['total_colors', 'color_scheme', 'accessibility_issues']); expect($analysis->conventionAnalysis)->toHaveKeys(['bem_compliance', 'naming_consistency', 'violations']); // Verify metrics expect($analysis->metrics)->toHaveKeys(['maturity_score', 'consistency_score', 'token_coverage']); expect($analysis->metrics['maturity_score'])->toBeBetween(0, 1); expect($analysis->metrics['consistency_score'])->toBeBetween(0, 1); // Verify recommendations expect($analysis->recommendations)->toBeArray(); expect($analysis->recommendations)->not->toBeEmpty(); // Verify gaps analysis expect($analysis->gaps)->toBeArray(); }); it('handles empty or invalid CSS gracefully', function () { $emptySystem = [ '/test/empty.css' => '', ]; $emptyAnalysis = $this->analyzer->analyze($emptySystem); expect($emptyAnalysis->overview['total_tokens'])->toBe(0); expect($emptyAnalysis->overview['total_components'])->toBe(0); expect($emptyAnalysis->maturityLevel)->toBe('Basic'); expect($emptyAnalysis->recommendations)->toContain('Start by defining design tokens'); $invalidSystem = [ '/test/invalid.css' => 'invalid css content {', ]; $invalidAnalysis = $this->analyzer->analyze($invalidSystem); expect($invalidAnalysis->errors)->not->toBeEmpty(); expect($invalidAnalysis->overview['total_tokens'])->toBe(0); }); it('tracks design system evolution over time', function () { $version1 = [ '/test/v1.css' => ' :root { --primary: #3b82f6; --secondary: #6b7280; } .btn { } .btn--primary { } ', ]; $version2 = [ '/test/v2.css' => ' :root { --color-primary-500: #3b82f6; --color-secondary-500: #6b7280; --spacing-md: 1rem; } .button { } .button--primary { } .button__icon { } ', ]; $v1Analysis = $this->analyzer->analyze($version1); $v2Analysis = $this->analyzer->analyze($version2); expect($v2Analysis->maturityScore)->toBeGreaterThan($v1Analysis->maturityScore); expect($v2Analysis->overview['total_tokens'])->toBeGreaterThan($v1Analysis->overview['total_tokens']); $evolution = $this->analyzer->compareVersions($v1Analysis, $v2Analysis); expect($evolution['improvements'])->not->toBeEmpty(); expect($evolution['regressions'])->toBeArray(); expect($evolution['new_features'])->toContain('Enhanced token naming'); expect($evolution['new_features'])->toContain('Added spacing tokens'); }); });