customPropertyParser = new CustomPropertyParser(); $this->classNameParser = new ClassNameParser(); $this->parser = new CssParser($this->customPropertyParser, $this->classNameParser); }); it('parses simple CSS content', function () { $css = ' :root { --primary-color: #3b82f6; --text-size: 16px; } .button { background-color: var(--primary-color); padding: 0.5rem 1rem; color: white; } '; $result = $this->parser->parseContent($css); expect($result->rules)->toHaveCount(2); expect($result->customProperties)->toHaveCount(2); expect($result->classNames)->toHaveCount(1); // Test custom properties $primaryColor = $result->customProperties[0]; expect($primaryColor->name)->toBe('primary-color'); expect($primaryColor->value)->toBe('#3b82f6'); // Test class names $buttonClass = $result->classNames[0]; expect($buttonClass->name)->toBe('button'); expect($buttonClass->isBemBlock())->toBeTrue(); }); it('parses BEM classes correctly', function () { $css = ' .card { } .card__header { } .card__body { } .card--featured { } .card__header--large { } '; $result = $this->parser->parseContent($css); expect($result->classNames)->toHaveCount(5); $classes = array_map(fn ($c) => $c->name, $result->classNames); expect($classes)->toContain('card'); expect($classes)->toContain('card__header'); expect($classes)->toContain('card__body'); expect($classes)->toContain('card--featured'); expect($classes)->toContain('card__header--large'); // Test BEM detection $cardClass = $result->classNames[0]; expect($cardClass->isBemBlock())->toBeTrue(); $headerClass = $result->classNames[1]; expect($headerClass->isBemElement())->toBeTrue(); $featuredClass = $result->classNames[3]; expect($featuredClass->isBemModifier())->toBeTrue(); }); it('handles OKLCH colors', function () { $css = ' :root { --modern-blue: oklch(0.7 0.15 260); --vibrant-red: oklch(65% 0.2 20deg); } '; $result = $this->parser->parseContent($css); expect($result->customProperties)->toHaveCount(2); $blueToken = $result->customProperties[0]; expect($blueToken->name)->toBe('modern-blue'); expect($blueToken->hasValueType('color'))->toBeTrue(); $color = $blueToken->getValueAs('color'); expect($color)->toBeInstanceOf(CssColor::class); expect($color->format)->toBe(ColorFormat::OKLCH); }); it('parses complex selectors', function () { $css = ' .nav-menu > .nav-item:hover .nav-link { color: #333; } @media (min-width: 768px) { .container { max-width: 1200px; } } '; $result = $this->parser->parseContent($css); expect($result->rules)->toHaveCount(2); // Complex selector $complexRule = $result->rules[0]; expect($complexRule->selectors)->toHaveCount(1); expect($complexRule->selectors[0]->value)->toContain('nav-menu'); expect($complexRule->selectors[0]->extractClasses())->toContain('nav-menu'); expect($complexRule->selectors[0]->extractClasses())->toContain('nav-item'); expect($complexRule->selectors[0]->extractClasses())->toContain('nav-link'); }); it('calculates selector specificity correctly', function () { $css = ' .simple { } #unique { } div.class { } .parent > .child:hover { } #main .sidebar .widget { } '; $result = $this->parser->parseContent($css); expect($result->rules)->toHaveCount(5); // .simple = 10 expect($result->rules[0]->selectors[0]->calculateSpecificity())->toBe(10); // #unique = 100 expect($result->rules[1]->selectors[0]->calculateSpecificity())->toBe(100); // div.class = 11 (1 element + 10 class) expect($result->rules[2]->selectors[0]->calculateSpecificity())->toBe(11); // .parent > .child:hover = 30 (20 classes + 10 pseudo-class) expect($result->rules[3]->selectors[0]->calculateSpecificity())->toBe(30); // #main .sidebar .widget = 120 (100 ID + 20 classes) expect($result->rules[4]->selectors[0]->calculateSpecificity())->toBe(120); }); it('parses file from filesystem', function () { // Create temporary CSS file $tempFile = sys_get_temp_dir() . '/test-' . uniqid() . '.css'; file_put_contents($tempFile, ' :root { --test-color: #ff0000; } .test-class { color: var(--test-color); } '); $filePath = new FilePath($tempFile); $result = $this->parser->parseFile($filePath); expect($result->customProperties)->toHaveCount(1); expect($result->classNames)->toHaveCount(1); expect($result->rules)->toHaveCount(2); // Cleanup unlink($tempFile); }); it('handles empty CSS gracefully', function () { $result = $this->parser->parseContent(''); expect($result->rules)->toHaveCount(0); expect($result->customProperties)->toHaveCount(0); expect($result->classNames)->toHaveCount(0); expect($result->statistics['total_rules'])->toBe(0); }); it('parses utility classes', function () { $css = ' .text-center { text-align: center; } .p-4 { padding: 1rem; } .bg-red-500 { background-color: #ef4444; } .hover\\:bg-blue-500:hover { background-color: #3b82f6; } '; $result = $this->parser->parseContent($css); expect($result->classNames)->toHaveCount(4); $classes = array_map(fn ($c) => $c->name, $result->classNames); expect($classes)->toContain('text-center'); expect($classes)->toContain('p-4'); expect($classes)->toContain('bg-red-500'); expect($classes)->toContain('hover:bg-blue-500'); // Test utility detection $utilityClass = $result->classNames[1]; // p-4 expect($utilityClass->isUtilityClass())->toBeTrue(); }); });