From f150006607b73766f9b72a05d978a2b63a4fa056 Mon Sep 17 00:00:00 2001 From: Dmitriy Zo Date: Fri, 8 Nov 2024 20:03:30 +0000 Subject: [PATCH] Additional fix for :nth-child(n+B) --- src/CSS/DOMTraverser/PseudoClass.php | 1 - src/CSS/DOMTraverser/Util.php | 2 + tests/QueryPath/CSS/PseudoClassTest.php | 168 +++++------------------- tests/QueryPath/CSS/UtilTest.php | 1 + 4 files changed, 38 insertions(+), 134 deletions(-) diff --git a/src/CSS/DOMTraverser/PseudoClass.php b/src/CSS/DOMTraverser/PseudoClass.php index de3bf2c5..bb273348 100644 --- a/src/CSS/DOMTraverser/PseudoClass.php +++ b/src/CSS/DOMTraverser/PseudoClass.php @@ -441,7 +441,6 @@ protected function isNthChild($node, $value, $reverse = false, $byType = false): $parent = $node->parentNode; if (empty($parent) || ($groupSize === 0 && $elementInGroup === 0) - || ($groupSize > 0 && $elementInGroup > $groupSize) ) { return false; } diff --git a/src/CSS/DOMTraverser/Util.php b/src/CSS/DOMTraverser/Util.php index c6751bd2..a7206b23 100644 --- a/src/CSS/DOMTraverser/Util.php +++ b/src/CSS/DOMTraverser/Util.php @@ -149,6 +149,8 @@ public static function parseAnB($rule): array $aVal = $matches[1] ?? 1; if ($aVal === '-') { $aVal = -1; + } elseif ($aVal === '') { + $aVal = 1; } else { $aVal = (int) $aVal; } diff --git a/tests/QueryPath/CSS/PseudoClassTest.php b/tests/QueryPath/CSS/PseudoClassTest.php index 583eb2d3..7893d906 100644 --- a/tests/QueryPath/CSS/PseudoClassTest.php +++ b/tests/QueryPath/CSS/PseudoClassTest.php @@ -428,7 +428,29 @@ public function testNthLastChild() $this->assertEquals(3, $i); } - public function testNthChild() + public function nthChildProvider(): array + { + return [ + ['2n+1', 10, ['a', 'c']], // Every odd row + ['odd', 10, ['a', 'c']], // Every odd row + ['2n', 10, ['b', 'd']], // Every even row + ['even', 10, ['b', 'd']], // Even (2n) + ['4n-1', 5, 'c' ], // 4n - 1 == 4n + 3 + ['6n-1', 3, null ], // 6n - 1 + ['26n-1', 0, null ], // 26n - 1 + ['0n+0', 0, null ], // 0n + 0 -- spec says this is always FALSE + ['3', 1, 'c' ], // 3 (0n+3) + ['-n+3', 3, null ], // -n+3: First three elements + ['n+3', 18, null ], // third+ elements + ['2n+4', 9, ['d', 'b']], // fourth+ even elements + ['6n+30', 0, null ], // 6n + 30. These should always fail to match + ]; + } + + /** + * @dataProvider nthChildProvider + */ + public function testNthChild($pattern, $matchesCount, $matchTag) { $xml = ''; $xml .= str_repeat('', 5); @@ -438,145 +460,25 @@ public function testNthChild() [$ele, $root] = $this->doc($xml, 'root'); $nl = $root->childNodes; - // 2n + 1 -- Every odd row. - $i = 0; - $expects = ['a', 'c']; - $j = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '2n+1'); - if ($res) { - ++$i; - $name = $n->tagName; - $this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j)); - } - } - $this->assertEquals(10, $i, '2n+1 is ten items.'); - - // Odd - $i = 0; - $expects = ['a', 'c']; - $j = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, 'odd'); - if ($res) { - ++$i; - $name = $n->tagName; - $this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j)); - } - } - $this->assertEquals(10, $i, '2n+1 is ten items.'); - - // 2n + 0 -- every even row - $i = 0; - $expects = ['b', 'd']; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '2n'); - if ($res) { - ++$i; - $name = $n->tagName; - $this->assertContains($name, $expects, 'Expected a or c, got ' . $name); - } - } - $this->assertEquals(10, $i, '2n+0 is ten items.'); - - // Even (2n) - $i = 0; - $expects = ['b', 'd']; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, 'even'); - if ($res) { - ++$i; - $name = $n->tagName; - $this->assertContains($name, $expects, 'Expected a or c, got ' . $name); - } - } - $this->assertEquals(10, $i, ' even is ten items.'); - - // 4n - 1 == 4n + 3 $i = 0; + $j = 0; foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '4n-1'); + $res = $ps->elementMatches('nth-child', $n, $root, $pattern); if ($res) { ++$i; $name = $n->tagName; - $this->assertEquals('c', $name, 'Expected c, got ' . $name); - } - } - $this->assertEquals(5, $i); - - // 6n - 1 - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '6n-1'); - if ($res) { - ++$i; - } - } - $this->assertEquals(3, $i); - - // 6n + 1 - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '6n+1'); - if ($res) { - ++$i; - } - } - $this->assertEquals(4, $i); - - // 26n - 1 - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '26n-1'); - if ($res) { - ++$i; - } - } - $this->assertEquals(0, $i); - - // 0n + 0 -- spec says this is always FALSE. - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '0n+0'); - if ($res) { - ++$i; - } - } - $this->assertEquals(0, $i); - - // 3 (0n+3) - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '3'); - if ($res) { - ++$i; - $this->assertEquals('c', $n->tagName); - } - } - $this->assertEquals(1, $i); - - // -n+3: First three elements. - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '-n+3'); - if ($res) { - ++$i; - //$this->assertEquals('c', $n->tagName); - } - } - $this->assertEquals(3, $i); - - // BROKEN RULES -- these should always fail to match. - - // 6n + 7; - $i = 0; - foreach ($nl as $n) { - $res = $ps->elementMatches('nth-child', $n, $root, '6n+7'); - if ($res) { - ++$i; + if (is_string($matchTag)) { + $this->assertEquals($matchTag, $name, 'Invalid tagName match'); + } elseif (is_array($matchTag)) { + $this->assertContains( + $name, + $matchTag, + 'Expected only ['.implode(', ', $matchTag).'] tags, got '.$name.' in slot '.++$j + ); + } } } - $this->assertEquals(0, $i); + $this->assertEquals($matchesCount, $i, 'Invalid matches count'); } public function testEven() diff --git a/tests/QueryPath/CSS/UtilTest.php b/tests/QueryPath/CSS/UtilTest.php index 81ce9d9c..7166aee8 100644 --- a/tests/QueryPath/CSS/UtilTest.php +++ b/tests/QueryPath/CSS/UtilTest.php @@ -45,6 +45,7 @@ public function testParseAnB() $this->assertEquals([2, -1], Util::parseAnB('2n - 1')); // -n + 3 $this->assertEquals([-1, 3], Util::parseAnB('-n+3')); + $this->assertEquals([1, 3], Util::parseAnB('n+3')); // Test invalid values $this->assertEquals([0, 0], Util::parseAnB('obviously + invalid'));