checker = new ConventionChecker(); }); it('validates BEM naming conventions', function () { $cssClasses = [ new CssClass('button'), // Valid: Block new CssClass('button__icon'), // Valid: Element new CssClass('button--primary'), // Valid: Modifier new CssClass('button__icon--large'), // Valid: Element modifier new CssClass('button_icon'), // Invalid: underscore instead of double new CssClass('button--primary--large'), // Invalid: double modifier new CssClass('BUTTON'), // Invalid: uppercase new CssClass('button__'), // Invalid: empty element new CssClass('button--'), // Invalid: empty modifier ]; $validation = $this->checker->validateBemNaming($cssClasses); expect($validation['valid'])->toHaveCount(4); expect($validation['invalid'])->toHaveCount(5); $invalidClasses = array_column($validation['invalid'], 'class'); expect($invalidClasses)->toContain('button_icon'); expect($invalidClasses)->toContain('button--primary--large'); expect($invalidClasses)->toContain('BUTTON'); }); it('checks kebab-case consistency', function () { $cssClasses = [ new CssClass('nav-menu'), // Valid new CssClass('user-profile'), // Valid new CssClass('search-input'), // Valid new CssClass('camelCase'), // Invalid new CssClass('PascalCase'), // Invalid new CssClass('snake_case'), // Invalid new CssClass('SCREAMING_CASE'), // Invalid new CssClass('123-invalid'), // Invalid: starts with number ]; $validation = $this->checker->validateKebabCase($cssClasses); expect($validation['valid'])->toHaveCount(3); expect($validation['invalid'])->toHaveCount(5); $violations = array_column($validation['invalid'], 'violation'); expect($violations)->toContain('camelCase detected'); expect($violations)->toContain('PascalCase detected'); expect($violations)->toContain('snake_case detected'); }); it('validates custom property naming', function () { $customProperties = [ new CustomProperty('primary-color', '#3b82f6'), // Valid new CustomProperty('text-base', '16px'), // Valid new CustomProperty('spacing-md', '1rem'), // Valid new CustomProperty('fontSize', '18px'), // Invalid: camelCase new CustomProperty('TEXT_SIZE', '20px'), // Invalid: UPPERCASE new CustomProperty('border_width', '1px'), // Invalid: snake_case new CustomProperty('123-invalid', '1px'), // Invalid: starts with number new CustomProperty('--invalid', 'value'), // Invalid: starts with -- ]; $validation = $this->checker->validateCustomPropertyNaming($customProperties); expect($validation['valid'])->toHaveCount(3); expect($validation['invalid'])->toHaveCount(5); $invalidNames = array_column($validation['invalid'], 'property'); expect($invalidNames)->toContain('fontSize'); expect($invalidNames)->toContain('TEXT_SIZE'); expect($invalidNames)->toContain('border_width'); }); it('checks semantic naming conventions', function () { $cssClasses = [ // Good semantic names new CssClass('header'), new CssClass('navigation'), new CssClass('content'), new CssClass('sidebar'), new CssClass('footer'), // Poor semantic names new CssClass('red-text'), // Presentational new CssClass('big-box'), // Presentational new CssClass('left-column'), // Positional new CssClass('div1'), // Generic new CssClass('thing'), // Vague ]; $validation = $this->checker->validateSemanticNaming($cssClasses); expect($validation['semantic'])->toHaveCount(5); expect($validation['presentational'])->toHaveCount(2); expect($validation['positional'])->toHaveCount(1); expect($validation['generic'])->toHaveCount(1); expect($validation['vague'])->toHaveCount(1); expect($validation['score'])->toBeCloseTo(0.5, 1); // 5/10 are semantic }); it('validates design token naming patterns', function () { $customProperties = [ // Good design system patterns new CustomProperty('color-primary-500', '#3b82f6'), new CustomProperty('spacing-xs', '0.25rem'), new CustomProperty('font-size-lg', '1.125rem'), new CustomProperty('border-radius-md', '0.375rem'), // Inconsistent patterns new CustomProperty('primary', '#3b82f6'), // Too generic new CustomProperty('blueColor', '#1d4ed8'), // camelCase new CustomProperty('spacing_small', '0.5rem'), // snake_case new CustomProperty('very-very-long-property-name-that-is-too-verbose', '1px'), ]; $validation = $this->checker->validateDesignTokenNaming($customProperties); expect($validation['consistent'])->toHaveCount(4); expect($validation['inconsistent'])->toHaveCount(4); $issues = array_column($validation['inconsistent'], 'issue'); expect($issues)->toContain('Too generic'); expect($issues)->toContain('Wrong case format'); expect($issues)->toContain('Too verbose'); }); it('checks accessibility naming conventions', function () { $cssClasses = [ // Good accessibility-focused names new CssClass('sr-only'), // Screen reader only new CssClass('visually-hidden'), // Visually hidden new CssClass('skip-link'), // Skip navigation new CssClass('focus-visible'), // Focus indicator // Potentially problematic new CssClass('hidden'), // Too generic new CssClass('invisible'), // Unclear intent new CssClass('no-display'), // Unclear semantics ]; $validation = $this->checker->validateAccessibilityNaming($cssClasses); expect($validation['accessibility_friendly'])->toHaveCount(4); expect($validation['potentially_problematic'])->toHaveCount(3); $recommendations = $validation['recommendations']; expect($recommendations)->toContain('Consider "visually-hidden" instead of "hidden"'); expect($recommendations)->toContain('Consider "sr-only" instead of "invisible"'); }); it('validates component naming hierarchy', function () { $cssClasses = [ // Good hierarchical naming new CssClass('card'), new CssClass('card__header'), new CssClass('card__title'), new CssClass('card__body'), new CssClass('card__footer'), // Poor hierarchical naming new CssClass('cardHeader'), // camelCase, no hierarchy new CssClass('card-title-text'), // Flat, not hierarchical new CssClass('header'), // Too generic when card__header exists ]; $validation = $this->checker->validateComponentHierarchy($cssClasses); expect($validation['well_structured'])->toHaveCount(5); expect($validation['poorly_structured'])->toHaveCount(3); $cardHierarchy = $validation['hierarchies']['card']; expect($cardHierarchy['elements'])->toContain('header'); expect($cardHierarchy['elements'])->toContain('title'); expect($cardHierarchy['depth'])->toBe(2); }); it('analyzes naming consistency across project', function () { $cssClasses = [ // Consistent button pattern new CssClass('btn'), new CssClass('btn--primary'), new CssClass('btn--secondary'), // Inconsistent button pattern new CssClass('button'), new CssClass('submit-button'), // Consistent form pattern new CssClass('form'), new CssClass('form__group'), new CssClass('form__label'), new CssClass('form__input'), ]; $consistency = $this->checker->analyzeNamingConsistency($cssClasses); expect($consistency['overall_score'])->toBeBetween(0.6, 0.8); expect($consistency['patterns']['btn']['consistency'])->toBe(1.0); expect($consistency['patterns']['button']['consistency'])->toBeLessThan(1.0); expect($consistency['inconsistencies'])->toContain('Mixed button naming: btn, button'); }); it('suggests naming improvements', function () { $cssClasses = [ new CssClass('redText'), // camelCase + presentational new CssClass('big_button'), // snake_case new CssClass('NAVIGATION'), // UPPERCASE new CssClass('div123'), // Generic + number new CssClass('thing'), // Vague ]; $suggestions = $this->checker->suggestNamingImprovements($cssClasses); expect($suggestions)->toHaveCount(5); $redTextSuggestion = collect($suggestions)->firstWhere('original', 'redText'); expect($redTextSuggestion['improved'])->toBe('error-text'); expect($redTextSuggestion['reasons'])->toContain('Convert to kebab-case'); expect($redTextSuggestion['reasons'])->toContain('Use semantic naming'); $bigButtonSuggestion = collect($suggestions)->firstWhere('original', 'big_button'); expect($bigButtonSuggestion['improved'])->toBe('button--large'); expect($bigButtonSuggestion['reasons'])->toContain('Convert to kebab-case'); expect($bigButtonSuggestion['reasons'])->toContain('Use BEM modifier pattern'); }); it('validates framework-specific conventions', function () { // Test Bootstrap-like conventions $bootstrapClasses = [ new CssClass('btn'), new CssClass('btn-primary'), new CssClass('btn-lg'), new CssClass('container'), new CssClass('row'), new CssClass('col-md-6'), ]; $bootstrapValidation = $this->checker->validateFrameworkConventions($bootstrapClasses, 'bootstrap'); expect($bootstrapValidation['compliant'])->toHaveCount(6); // Test Tailwind-like conventions $tailwindClasses = [ new CssClass('text-center'), new CssClass('bg-blue-500'), new CssClass('p-4'), new CssClass('hover:bg-blue-600'), new CssClass('sm:text-left'), ]; $tailwindValidation = $this->checker->validateFrameworkConventions($tailwindClasses, 'tailwind'); expect($tailwindValidation['compliant'])->toHaveCount(5); // Test BEM conventions $bemClasses = [ new CssClass('block'), new CssClass('block__element'), new CssClass('block--modifier'), new CssClass('block__element--modifier'), ]; $bemValidation = $this->checker->validateFrameworkConventions($bemClasses, 'bem'); expect($bemValidation['compliant'])->toHaveCount(4); }); });