detector = new ComponentDetector(); }); it('detects BEM components correctly', function () { $cssClasses = [ new CssClass('card'), new CssClass('card__header'), new CssClass('card__body'), new CssClass('card__footer'), new CssClass('card--featured'), new CssClass('card__header--large'), new CssClass('button'), new CssClass('button--primary'), ]; $components = $this->detector->detectBemComponents($cssClasses); expect($components)->toHaveCount(2); // card and button $cardComponent = $components[0]; expect($cardComponent['block'])->toBe('card'); expect($cardComponent['elements'])->toContain('header'); expect($cardComponent['elements'])->toContain('body'); expect($cardComponent['elements'])->toContain('footer'); expect($cardComponent['modifiers'])->toContain('featured'); expect($cardComponent['element_modifiers'])->toHaveKey('header'); expect($cardComponent['element_modifiers']['header'])->toContain('large'); }); it('identifies utility class patterns', function () { $cssClasses = [ new CssClass('text-center'), new CssClass('text-left'), new CssClass('text-right'), new CssClass('p-4'), new CssClass('p-8'), new CssClass('m-2'), new CssClass('bg-blue-500'), new CssClass('bg-red-300'), new CssClass('hover:bg-blue-600'), new CssClass('card'), // Not a utility ]; $utilityPatterns = $this->detector->detectUtilityPatterns($cssClasses); expect($utilityPatterns)->toHaveKey('text-alignment'); expect($utilityPatterns)->toHaveKey('padding'); expect($utilityPatterns)->toHaveKey('margin'); expect($utilityPatterns)->toHaveKey('background-color'); expect($utilityPatterns)->toHaveKey('hover-states'); expect($utilityPatterns['text-alignment'])->toHaveCount(3); expect($utilityPatterns['padding'])->toHaveCount(2); expect($utilityPatterns['background-color'])->toHaveCount(2); expect($utilityPatterns['hover-states'])->toHaveCount(1); }); it('detects component structure patterns', function () { $cssClasses = [ // Layout components new CssClass('container'), new CssClass('row'), new CssClass('col'), new CssClass('col-md-6'), // Form components new CssClass('form'), new CssClass('form-group'), new CssClass('form-control'), new CssClass('form-label'), // Navigation components new CssClass('nav'), new CssClass('nav-item'), new CssClass('nav-link'), ]; $patterns = $this->detector->detectStructurePatterns($cssClasses); expect($patterns)->toHaveKey('layout'); expect($patterns)->toHaveKey('form'); expect($patterns)->toHaveKey('navigation'); expect($patterns['layout']['components'])->toContain('container'); expect($patterns['layout']['components'])->toContain('row'); expect($patterns['form']['components'])->toContain('form'); expect($patterns['form']['components'])->toContain('form-group'); expect($patterns['navigation']['components'])->toContain('nav'); }); it('analyzes responsive design patterns', function () { $cssClasses = [ new CssClass('hidden-xs'), new CssClass('visible-md'), new CssClass('col-sm-12'), new CssClass('col-md-6'), new CssClass('col-lg-4'), new CssClass('text-sm-center'), new CssClass('text-md-left'), ]; $responsive = $this->detector->analyzeResponsivePatterns($cssClasses); expect($responsive['breakpoints'])->toContain('xs'); expect($responsive['breakpoints'])->toContain('sm'); expect($responsive['breakpoints'])->toContain('md'); expect($responsive['breakpoints'])->toContain('lg'); expect($responsive['patterns']['visibility'])->toHaveCount(2); expect($responsive['patterns']['grid'])->toHaveCount(3); expect($responsive['patterns']['typography'])->toHaveCount(2); }); it('identifies component complexity levels', function () { $simpleComponent = [ new CssClass('button'), new CssClass('button--primary'), ]; $complexComponent = [ new CssClass('card'), new CssClass('card__header'), new CssClass('card__title'), new CssClass('card__subtitle'), new CssClass('card__body'), new CssClass('card__content'), new CssClass('card__actions'), new CssClass('card__footer'), new CssClass('card--featured'), new CssClass('card--compact'), new CssClass('card__header--large'), new CssClass('card__actions--centered'), ]; $simpleComplexity = $this->detector->analyzeComponentComplexity($simpleComponent); $complexComplexity = $this->detector->analyzeComponentComplexity($complexComponent); expect($simpleComplexity['level'])->toBe('simple'); expect($simpleComplexity['score'])->toBeLessThan(3); expect($complexComplexity['level'])->toBe('complex'); expect($complexComplexity['score'])->toBeGreaterThan(8); expect($complexComplexity['recommendations'])->toContain('Consider splitting into smaller components'); }); it('detects atomic design patterns', function () { $cssClasses = [ // Atoms new CssClass('btn'), new CssClass('input'), new CssClass('label'), new CssClass('icon'), // Molecules new CssClass('search-form'), new CssClass('form-group'), new CssClass('nav-item'), // Organisms new CssClass('header'), new CssClass('sidebar'), new CssClass('footer'), new CssClass('product-grid'), ]; $atomicAnalysis = $this->detector->analyzeAtomicDesignPatterns($cssClasses); expect($atomicAnalysis['atoms'])->toHaveCount(4); expect($atomicAnalysis['molecules'])->toHaveCount(3); expect($atomicAnalysis['organisms'])->toHaveCount(4); expect($atomicAnalysis['atoms'])->toContain('btn'); expect($atomicAnalysis['molecules'])->toContain('search-form'); expect($atomicAnalysis['organisms'])->toContain('header'); }); it('validates component naming conventions', function () { $cssClasses = [ new CssClass('button'), // Good: semantic new CssClass('btn'), // Good: abbreviation new CssClass('redButton'), // Bad: camelCase new CssClass('button_primary'), // Bad: underscore instead of dash new CssClass('Button'), // Bad: PascalCase new CssClass('my-custom-btn-2'), // Good: kebab-case ]; $validation = $this->detector->validateNamingConventions($cssClasses); expect($validation['valid'])->toHaveCount(3); expect($validation['invalid'])->toHaveCount(3); $invalidClasses = array_map(fn ($v) => $v['class'], $validation['invalid']); expect($invalidClasses)->toContain('redButton'); expect($invalidClasses)->toContain('button_primary'); expect($invalidClasses)->toContain('Button'); }); it('detects component relationships', function () { $cssClasses = [ new CssClass('modal'), new CssClass('modal__backdrop'), new CssClass('modal__dialog'), new CssClass('modal__header'), new CssClass('modal__title'), new CssClass('modal__close'), new CssClass('modal__body'), new CssClass('modal__footer'), new CssClass('modal__actions'), ]; $relationships = $this->detector->detectComponentRelationships($cssClasses); expect($relationships)->toHaveKey('modal'); $modalRelationships = $relationships['modal']; expect($modalRelationships['children'])->toContain('backdrop'); expect($modalRelationships['children'])->toContain('dialog'); expect($modalRelationships['children'])->toContain('header'); expect($modalRelationships['depth'])->toBe(2); // modal -> header -> title expect($modalRelationships['complexity_score'])->toBeGreaterThan(5); }); it('suggests component improvements', function () { $cssClasses = [ // Inconsistent button pattern new CssClass('button'), new CssClass('btn'), // Inconsistent naming new CssClass('submit-btn'), // Another variation // Missing BEM structure new CssClass('card-header'), // Should be card__header new CssClass('card-body'), // Should be card__body // Overly specific new CssClass('red-submit-button-large'), ]; $improvements = $this->detector->suggestImprovements($cssClasses); expect($improvements['naming_inconsistencies'])->not->toBeEmpty(); expect($improvements['bem_violations'])->not->toBeEmpty(); expect($improvements['overly_specific'])->not->toBeEmpty(); expect($improvements['suggestions'])->toContain('Standardize button naming (choose: button, btn)'); expect($improvements['suggestions'])->toContain('Convert card-header to card__header for BEM compliance'); }); it('analyzes component reusability', function () { $cssClasses = [ new CssClass('btn'), new CssClass('btn--primary'), new CssClass('btn--secondary'), new CssClass('btn--large'), new CssClass('btn--small'), new CssClass('very-specific-page-button'), // Low reusability ]; $reusability = $this->detector->analyzeComponentReusability($cssClasses); $btnReusability = $reusability['btn']; expect($btnReusability['score'])->toBeGreaterThan(0.8); expect($btnReusability['variants'])->toBe(4); expect($btnReusability['reusability_level'])->toBe('high'); $specificReusability = $reusability['very-specific-page-button']; expect($specificReusability['score'])->toBeLessThan(0.3); expect($specificReusability['reusability_level'])->toBe('low'); }); });