- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
277 lines
12 KiB
PHP
277 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Design\Service\ConventionChecker;
|
|
use App\Framework\Design\ValueObjects\CssClass;
|
|
use App\Framework\Design\ValueObjects\CustomProperty;
|
|
|
|
describe('ConventionChecker', function () {
|
|
|
|
beforeEach(function () {
|
|
$this->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);
|
|
});
|
|
});
|