- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
347 lines
13 KiB
PHP
347 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Design\Parser\ClassNameParser;
|
|
use App\Framework\Design\Parser\CssParser;
|
|
use App\Framework\Design\Parser\CustomPropertyParser;
|
|
use App\Framework\Design\Service\ColorAnalyzer;
|
|
use App\Framework\Design\Service\ComponentDetector;
|
|
use App\Framework\Design\Service\ConventionChecker;
|
|
use App\Framework\Design\Service\DesignSystemAnalyzer;
|
|
use App\Framework\Design\Service\TokenAnalyzer;
|
|
|
|
describe('DesignSystemAnalyzer', function () {
|
|
|
|
beforeEach(function () {
|
|
$this->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');
|
|
});
|
|
});
|