generated from jric11/baseProject
Initial commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CascadeContextTest.php
|
||||
*
|
||||
* @since 2002-08-03
|
||||
* @category Library
|
||||
* @package Pdf
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf
|
||||
*
|
||||
* This file is part of tc-lib-pdf software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use Com\Tecnick\Pdf\CSS\CascadeContext;
|
||||
|
||||
class CascadeContextTest extends TestUtil
|
||||
{
|
||||
public function testInitialStateHasZeroSourceOrder(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
$this->assertSame(0, $ctx->getTotalRulesProcessed());
|
||||
}
|
||||
|
||||
public function testGetNextNormalSourceOrderIncrementsCounter(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
$order1 = $ctx->getNextNormalSourceOrder();
|
||||
$order2 = $ctx->getNextNormalSourceOrder();
|
||||
$order3 = $ctx->getNextNormalSourceOrder();
|
||||
|
||||
$this->assertSame(1, $order1);
|
||||
$this->assertSame(2, $order2);
|
||||
$this->assertSame(3, $order3);
|
||||
$this->assertSame(3, $ctx->getTotalRulesProcessed());
|
||||
}
|
||||
|
||||
public function testGetNextImportantSourceOrderStartsAboveNormalRange(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
// Normal rules
|
||||
$ctx->getNextNormalSourceOrder();
|
||||
$ctx->getNextNormalSourceOrder();
|
||||
|
||||
// First important rule should start at MIN_IMPORTANT_SOURCE_ORDER
|
||||
$importantOrder = $ctx->getNextImportantSourceOrder();
|
||||
$this->assertSame(CascadeContext::MIN_IMPORTANT_SOURCE_ORDER + 1, $importantOrder);
|
||||
$this->assertGreaterThan(CascadeContext::INLINE_STYLE_SOURCE_ORDER, $importantOrder);
|
||||
}
|
||||
|
||||
public function testImportantAndNormalCountersAreIndependent(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
$normal1 = $ctx->getNextNormalSourceOrder();
|
||||
$important1 = $ctx->getNextImportantSourceOrder();
|
||||
$normal2 = $ctx->getNextNormalSourceOrder();
|
||||
$important2 = $ctx->getNextImportantSourceOrder();
|
||||
|
||||
$this->assertSame(1, $normal1);
|
||||
$this->assertSame(CascadeContext::MIN_IMPORTANT_SOURCE_ORDER + 1, $important1);
|
||||
$this->assertSame(2, $normal2);
|
||||
$this->assertSame(CascadeContext::MIN_IMPORTANT_SOURCE_ORDER + 2, $important2);
|
||||
}
|
||||
|
||||
public function testInlineStyleSourceOrderIsMaximumNormalValue(): void
|
||||
{
|
||||
$inlineOrder = CascadeContext::getInlineStyleSourceOrder();
|
||||
|
||||
$this->assertSame(10000000, $inlineOrder);
|
||||
$this->assertGreaterThan(CascadeContext::MAX_NORMAL_SOURCE_ORDER, $inlineOrder);
|
||||
$this->assertLessThan(CascadeContext::MIN_IMPORTANT_SOURCE_ORDER, $inlineOrder);
|
||||
}
|
||||
|
||||
public function testSetCurrentSourceTypeStoresAndRetrievesValue(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
$this->assertSame('embedded', $ctx->getCurrentSourceType());
|
||||
|
||||
$ctx->setCurrentSourceType('external');
|
||||
$this->assertSame('external', $ctx->getCurrentSourceType());
|
||||
|
||||
$ctx->setCurrentSourceType('inline');
|
||||
$this->assertSame('inline', $ctx->getCurrentSourceType());
|
||||
}
|
||||
|
||||
public function testResetClearsAllCountersAndState(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
// Add some rules
|
||||
$ctx->setCurrentSourceType('external');
|
||||
$ctx->getNextNormalSourceOrder();
|
||||
$ctx->getNextNormalSourceOrder();
|
||||
$ctx->getNextImportantSourceOrder();
|
||||
|
||||
$this->assertGreaterThan(0, $ctx->getTotalRulesProcessed());
|
||||
$this->assertSame('external', $ctx->getCurrentSourceType());
|
||||
|
||||
// Reset
|
||||
$ctx->reset();
|
||||
|
||||
$this->assertSame(0, $ctx->getTotalRulesProcessed());
|
||||
$this->assertSame('embedded', $ctx->getCurrentSourceType());
|
||||
}
|
||||
|
||||
public function testMultipleSourcesCanBeProcessedSequentially(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
// External stylesheet: 3 rules
|
||||
$ctx->setCurrentSourceType('external');
|
||||
$ctx->getNextNormalSourceOrder();
|
||||
$ctx->getNextNormalSourceOrder();
|
||||
$ext3 = $ctx->getNextNormalSourceOrder();
|
||||
|
||||
// Embedded style: 2 rules
|
||||
$ctx->setCurrentSourceType('embedded');
|
||||
$emb1 = $ctx->getNextNormalSourceOrder();
|
||||
$emb2 = $ctx->getNextNormalSourceOrder();
|
||||
|
||||
// Inline style: 1 rule
|
||||
$ctx->setCurrentSourceType('inline');
|
||||
$inl1 = $ctx->getNextNormalSourceOrder();
|
||||
|
||||
// Verify ordering: external < embedded < inline
|
||||
$this->assertLessThan($emb1, $ext3);
|
||||
$this->assertLessThan($inl1, $emb2);
|
||||
$this->assertSame(6, $ctx->getTotalRulesProcessed());
|
||||
}
|
||||
|
||||
public function testSourceOrderValuesRemainBelowImportantRange(): void
|
||||
{
|
||||
$ctx = new CascadeContext();
|
||||
|
||||
// Generate many normal rules
|
||||
for ($i = 0; $i < 100; ++$i) {
|
||||
$order = $ctx->getNextNormalSourceOrder();
|
||||
$this->assertLessThanOrEqual(
|
||||
CascadeContext::MAX_NORMAL_SOURCE_ORDER,
|
||||
$order,
|
||||
"Normal source order exceeded max at iteration {$i}",
|
||||
);
|
||||
}
|
||||
|
||||
// Important rules should still be higher
|
||||
$importantOrder = $ctx->getNextImportantSourceOrder();
|
||||
$this->assertGreaterThan(CascadeContext::MAX_NORMAL_SOURCE_ORDER, $importantOrder);
|
||||
}
|
||||
|
||||
public function testCascadePrecedenceForDeterminism(): void
|
||||
{
|
||||
// This test verifies the precedence model:
|
||||
// normal rules < inline styles < !important rules
|
||||
|
||||
$normalMax = CascadeContext::MAX_NORMAL_SOURCE_ORDER;
|
||||
$inlineOrder = CascadeContext::getInlineStyleSourceOrder();
|
||||
$importantMin = CascadeContext::MIN_IMPORTANT_SOURCE_ORDER;
|
||||
|
||||
// Verify cascade ordering: normal < inline < important
|
||||
$this->assertLessThan($inlineOrder, $normalMax);
|
||||
$this->assertLessThan($importantMin, $inlineOrder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ImportanceNormalizerTest.php
|
||||
*
|
||||
* @since 2026-05-08
|
||||
* @category Library
|
||||
* @package Pdf
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf
|
||||
*
|
||||
* This file is part of tc-lib-pdf software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use Com\Tecnick\Pdf\CSS\ImportanceNormalizer;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
/**
|
||||
* Test ImportanceNormalizer CSS declaration normalization
|
||||
*/
|
||||
class ImportanceNormalizerTest extends TestUtil
|
||||
{
|
||||
public function testGetAffectedLonghandsExpandsNestedBorderAliases(): void
|
||||
{
|
||||
$affected = ImportanceNormalizer::getAffectedLonghands('border');
|
||||
|
||||
$this->assertContains('border-width', $affected);
|
||||
$this->assertContains('border-style', $affected);
|
||||
$this->assertContains('border-color', $affected);
|
||||
$this->assertContains('border-top-width', $affected);
|
||||
$this->assertContains('border-right-style', $affected);
|
||||
$this->assertContains('border-bottom-color', $affected);
|
||||
$this->assertContains('border-left-width', $affected);
|
||||
}
|
||||
|
||||
public function testNormalizePreservesRegularDeclarations(): void
|
||||
{
|
||||
$input = 'color: red; font-size: 12px;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$this->assertStringContainsString('color:red;', \str_replace(' ', '', $result));
|
||||
$this->assertStringContainsString('font-size:12px;', \str_replace(' ', '', $result));
|
||||
$this->assertStringNotContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizePreservesImportantFlag(): void
|
||||
{
|
||||
$input = 'color: red !important; font-size: 12px;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$this->assertStringContainsString('color:red!important;', \str_replace(' ', '', $result));
|
||||
$this->assertStringNotContainsString('font-size:12px!important;', \str_replace(' ', '', $result));
|
||||
}
|
||||
|
||||
public function testNormalizeBorderShorthandWithoutImportant(): void
|
||||
{
|
||||
$input = 'border: 1px solid red;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
// Should contain the shorthand property
|
||||
$this->assertStringContainsString('border:', $result);
|
||||
$this->assertStringNotContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeBorderShorthandWithImportant(): void
|
||||
{
|
||||
$input = 'border: 1px solid red !important;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
// The shorthand should have !important
|
||||
$this->assertStringContainsString('border:', $result);
|
||||
$this->assertStringContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeMixedShorthandAndLonghands(): void
|
||||
{
|
||||
$input = 'margin: 10px !important; margin-top: 20px;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
// Should contain both margin shorthand and the longhand
|
||||
$this->assertStringContainsString('margin:', $result);
|
||||
$this->assertStringContainsString('margin-top:', $result);
|
||||
// Shorthand !important should be preserved
|
||||
$this->assertStringContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizePaddingShorthandWithImportant(): void
|
||||
{
|
||||
$input = 'padding: 5px 10px !important;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$this->assertStringContainsString('padding:', $result);
|
||||
$this->assertStringContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeEmptyString(): void
|
||||
{
|
||||
$result = ImportanceNormalizer::normalize('');
|
||||
$this->assertSame('', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeHandlesWhitespaceAroundImportant(): void
|
||||
{
|
||||
$input = 'color: blue ! important;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$this->assertStringContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeMultipleImportantDeclarations(): void
|
||||
{
|
||||
$input = 'color: red !important; background: blue !important; font-size: 14px;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$normalized = \str_replace(' ', '', $result);
|
||||
$this->assertStringContainsString('color:red!important;', $normalized);
|
||||
$this->assertStringContainsString('background:blue!important;', $normalized);
|
||||
$this->assertStringNotContainsString('font-size:14px!important;', $normalized);
|
||||
}
|
||||
|
||||
public function testNormalizeHandlesTrailingSemicolon(): void
|
||||
{
|
||||
$input = 'color: red;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$this->assertStringEndsWith(';', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeCaseInsensitive(): void
|
||||
{
|
||||
$input = 'COLOR: red !IMPORTANT;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
// Property should be lowercased
|
||||
$this->assertStringContainsString('color:', $result);
|
||||
// !important flag should be present (case-insensitive)
|
||||
$this->assertStringContainsString('!important', $result);
|
||||
}
|
||||
|
||||
public function testNormalizeSkipsMalformedDeclarationSegments(): void
|
||||
{
|
||||
$input = 'broken; :12px; color: red;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$normalized = \str_replace(' ', '', $result);
|
||||
$this->assertSame('color:red;', $normalized);
|
||||
}
|
||||
|
||||
public function testNormalizePromotesExistingLonghandWhenShorthandImportantAppearsLater(): void
|
||||
{
|
||||
$input = 'margin-top: 2px; margin: 10px !important;';
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$normalized = \str_replace(' ', '', $result);
|
||||
$this->assertStringContainsString('margin-top:2px!important;', $normalized);
|
||||
$this->assertStringContainsString('margin:10px!important;', $normalized);
|
||||
}
|
||||
|
||||
#[DataProvider('realWorldDeclarationsProvider')]
|
||||
public function testNormalizeRealWorldDeclarations(string $input, string $expectedProperty): void
|
||||
{
|
||||
$result = ImportanceNormalizer::normalize($input);
|
||||
|
||||
$this->assertStringContainsString($expectedProperty, $result);
|
||||
}
|
||||
|
||||
/** @return array<string, array{0: string, 1: string}> */
|
||||
public static function realWorldDeclarationsProvider(): array
|
||||
{
|
||||
return [
|
||||
'button_styling' => [
|
||||
'padding: 10px 20px !important; color: white; background: blue;',
|
||||
'padding:',
|
||||
],
|
||||
'text_emphasis' => [
|
||||
'font-weight: bold !important; font-size: 14px;',
|
||||
'font-weight:',
|
||||
],
|
||||
'border_reset' => [
|
||||
'border: none !important; margin: 0;',
|
||||
'border:',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SpecificityTest.php
|
||||
*
|
||||
* @since 2002-08-03
|
||||
* @category Library
|
||||
* @package Pdf
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf
|
||||
*
|
||||
* This file is part of tc-lib-pdf software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use Com\Tecnick\Pdf\CSS\Specificity;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class SpecificityTest extends TestUtil
|
||||
{
|
||||
public function testConstructorInitializesValues(): void
|
||||
{
|
||||
$spec = new Specificity(1, 2, 3);
|
||||
$this->assertSame(1, $spec->idCount);
|
||||
$this->assertSame(2, $spec->classCount);
|
||||
$this->assertSame(3, $spec->typeCount);
|
||||
}
|
||||
|
||||
public function testConstructorNormalizesNegativeValues(): void
|
||||
{
|
||||
$spec = new Specificity(-1, -2, -3);
|
||||
$this->assertSame(0, $spec->idCount);
|
||||
$this->assertSame(0, $spec->classCount);
|
||||
$this->assertSame(0, $spec->typeCount);
|
||||
}
|
||||
|
||||
#[DataProvider('selectorSpecificityProvider')]
|
||||
public function testFromSelectorCalculatesCorrectSpecificity(
|
||||
string $selector,
|
||||
int $expectedA,
|
||||
int $expectedB,
|
||||
int $expectedC,
|
||||
): void {
|
||||
$spec = Specificity::fromSelector($selector);
|
||||
$this->assertSame($expectedA, $spec->idCount, "ID count mismatch for: {$selector}");
|
||||
$this->assertSame(
|
||||
$expectedB,
|
||||
$spec->classCount,
|
||||
"Class/attribute/pseudo-class count mismatch for: {$selector}",
|
||||
);
|
||||
$this->assertSame($expectedC, $spec->typeCount, "Type/pseudo-element count mismatch for: {$selector}");
|
||||
}
|
||||
|
||||
/** @return array<string, array{0: string, 1: int, 2: int, 3: int}> */
|
||||
public static function selectorSpecificityProvider(): array
|
||||
{
|
||||
return [
|
||||
'universal selector' => ['*', 0, 0, 0],
|
||||
'single type' => ['div', 0, 0, 1],
|
||||
'single class' => ['.highlight', 0, 1, 0],
|
||||
'single id' => ['#main', 1, 0, 0],
|
||||
'type with class' => ['div.highlight', 0, 1, 1],
|
||||
'type with id' => ['div#main', 1, 0, 1],
|
||||
'type with attribute' => ['div[role="button"]', 0, 1, 1],
|
||||
'type with pseudo-class' => ['div:hover', 0, 1, 1],
|
||||
'descendant combinator' => ['div p', 0, 0, 2],
|
||||
'child combinator' => ['div > p', 0, 0, 2],
|
||||
'adjacent sibling' => ['h1 + p', 0, 0, 2],
|
||||
'general sibling' => ['h1 ~ p', 0, 0, 2],
|
||||
'multiple ids' => ['#main #article', 2, 0, 0],
|
||||
'multiple classes' => ['.header.highlight.active', 0, 3, 0],
|
||||
'complex selector 1' => ['.header nav li a:hover', 0, 2, 3],
|
||||
'complex selector 2' => ['#nav .menu li a:visited', 1, 2, 2],
|
||||
'multiple pseudo-classes' => ['a:link:visited:hover', 0, 3, 1],
|
||||
'pseudo-element before' => ['p::before', 0, 0, 2],
|
||||
'pseudo-element after' => ['p::after', 0, 0, 2],
|
||||
'type and pseudo-element' => ['div::before', 0, 0, 2],
|
||||
'multiple attributes' => ['div[data-foo][data-bar]', 0, 2, 1],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('specificityComparisonProvider')]
|
||||
public function testComparisonMethods(
|
||||
int $idCount1,
|
||||
int $classCount1,
|
||||
int $typeCount1,
|
||||
int $idCount2,
|
||||
int $classCount2,
|
||||
int $typeCount2,
|
||||
int $expectedComparison,
|
||||
): void {
|
||||
$spec1 = new Specificity($idCount1, $classCount1, $typeCount1);
|
||||
$spec2 = new Specificity($idCount2, $classCount2, $typeCount2);
|
||||
|
||||
$result = $spec1->compareTo($spec2);
|
||||
$this->assertSame($expectedComparison, $result);
|
||||
|
||||
if ($expectedComparison < 0) {
|
||||
$this->assertTrue($spec1->isLessThan($spec2));
|
||||
$this->assertFalse($spec1->isGreaterThan($spec2));
|
||||
$this->assertFalse($spec1->equals($spec2));
|
||||
} elseif ($expectedComparison > 0) {
|
||||
$this->assertTrue($spec1->isGreaterThan($spec2));
|
||||
$this->assertFalse($spec1->isLessThan($spec2));
|
||||
$this->assertFalse($spec1->equals($spec2));
|
||||
} else {
|
||||
$this->assertTrue($spec1->equals($spec2));
|
||||
$this->assertFalse($spec1->isLessThan($spec2));
|
||||
$this->assertFalse($spec1->isGreaterThan($spec2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int}>
|
||||
*/
|
||||
public static function specificityComparisonProvider(): array
|
||||
{
|
||||
return [
|
||||
'equal' => [0, 0, 0, 0, 0, 0, 0],
|
||||
'a determines precedence' => [1, 0, 0, 0, 9, 9, 1],
|
||||
'b determines when a equal' => [0, 1, 0, 0, 0, 9, 1],
|
||||
'c determines when a and b equal' => [0, 0, 1, 0, 0, 0, 1],
|
||||
'less: all components' => [0, 0, 0, 1, 1, 1, -1],
|
||||
'greater: all components' => [1, 1, 1, 0, 0, 0, 1],
|
||||
'edge case: high b vs low a' => [0, 99, 99, 1, 0, 0, -1],
|
||||
'edge case: high c vs low a' => [0, 0, 99, 1, 0, 0, -1],
|
||||
'edge case: all high vs single a' => [0, 99, 99, 1, 0, 0, -1],
|
||||
];
|
||||
}
|
||||
|
||||
public function testSortKeyFormatting(): void
|
||||
{
|
||||
$spec = new Specificity(1, 2, 3);
|
||||
$key = $spec->toSortKey(0);
|
||||
$this->assertStringMatchesFormat('%s_%s', $key);
|
||||
$this->assertStringContainsString('0001', $key);
|
||||
$this->assertStringContainsString('0002', $key);
|
||||
$this->assertStringContainsString('0003', $key);
|
||||
$this->assertStringContainsString('000000', $key);
|
||||
}
|
||||
|
||||
public function testSortKeyWithSourceOrder(): void
|
||||
{
|
||||
$spec = new Specificity(1, 2, 3);
|
||||
$key1 = $spec->toSortKey(0);
|
||||
$key2 = $spec->toSortKey(1);
|
||||
|
||||
// Same specificity, different source order - should be ordered by index
|
||||
$this->assertLessThan(0, \strcmp($key1, $key2));
|
||||
}
|
||||
|
||||
public function testSortKeyOrderingBySpecificity(): void
|
||||
{
|
||||
$low = new Specificity(0, 0, 1);
|
||||
$medium = new Specificity(0, 1, 0);
|
||||
$high = new Specificity(1, 0, 0);
|
||||
|
||||
$keys = [
|
||||
$high->toSortKey(0),
|
||||
$low->toSortKey(0),
|
||||
$medium->toSortKey(0),
|
||||
];
|
||||
|
||||
$sorted = $keys;
|
||||
\sort($sorted);
|
||||
|
||||
// After string sort, low < medium < high
|
||||
$this->assertSame($low->toSortKey(0), $sorted[0]);
|
||||
assert(isset($sorted[1]), "\$sorted[1] must be set");
|
||||
$this->assertSame($medium->toSortKey(0), $sorted[1]);
|
||||
assert(isset($sorted[2]), "\$sorted[2] must be set");
|
||||
$this->assertSame($high->toSortKey(0), $sorted[2]);
|
||||
}
|
||||
|
||||
public function testToStringRepresentation(): void
|
||||
{
|
||||
$spec = new Specificity(1, 2, 3);
|
||||
$str = $spec->toString();
|
||||
$this->assertSame('(1,2,3)', $str);
|
||||
}
|
||||
|
||||
public function testLegacyStringConversion(): void
|
||||
{
|
||||
$spec = new Specificity(1, 2, 3);
|
||||
$legacy = $spec->toLegacyString(0);
|
||||
$this->assertSame('0123', $legacy);
|
||||
|
||||
$legacyInline = $spec->toLegacyString(1);
|
||||
$this->assertSame('1123', $legacyInline);
|
||||
}
|
||||
|
||||
public function testLegacyStringParsing(): void
|
||||
{
|
||||
$spec = Specificity::fromLegacyString('0123');
|
||||
$this->assertSame(1, $spec->idCount);
|
||||
$this->assertSame(2, $spec->classCount);
|
||||
$this->assertSame(3, $spec->typeCount);
|
||||
}
|
||||
|
||||
public function testLegacyStringRoundTrip(): void
|
||||
{
|
||||
$original = new Specificity(2, 3, 4);
|
||||
$legacy = $original->toLegacyString(0);
|
||||
$restored = Specificity::fromLegacyString($legacy);
|
||||
|
||||
$this->assertTrue($original->equals($restored));
|
||||
}
|
||||
|
||||
public function testRealWorldSelectorComparisons(): void
|
||||
{
|
||||
// From CSS spec examples
|
||||
$headingOne = Specificity::fromSelector('h1');
|
||||
$h1_foo = Specificity::fromSelector('h1.foo');
|
||||
$h1_foo_bar = Specificity::fromSelector('h1.foo.bar');
|
||||
$div_p_a = Specificity::fromSelector('div p a');
|
||||
$foo_bar_baz = Specificity::fromSelector('.foo .bar .baz');
|
||||
$id_selector = Specificity::fromSelector('#main');
|
||||
|
||||
// h1 (0,0,1) < h1.foo (0,1,1)
|
||||
$this->assertTrue($headingOne->isLessThan($h1_foo));
|
||||
|
||||
// h1.foo (0,1,1) < h1.foo.bar (0,2,1)
|
||||
$this->assertTrue($h1_foo->isLessThan($h1_foo_bar));
|
||||
|
||||
// div p a (0,0,3) < .foo .bar .baz (0,3,0)
|
||||
$this->assertTrue($div_p_a->isLessThan($foo_bar_baz));
|
||||
|
||||
// All < #main (1,0,0)
|
||||
$this->assertTrue($foo_bar_baz->isLessThan($id_selector));
|
||||
$this->assertTrue($h1_foo_bar->isLessThan($id_selector));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user