Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
346
tests/Framework/Design/Service/DesignSystemAnalyzerTest.php
Normal file
346
tests/Framework/Design/Service/DesignSystemAnalyzerTest.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user