diff --git a/.gitignore b/.gitignore index e1d95c1a..32df0b84 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ docs/* doc/* test/fakepear vendor/ -composer.lock \ No newline at end of file +composer.lock +.phpunit.result.cache +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f201ec..810f0c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ QueryPath Changelog # 4.0.1 - Only define global functions qp(), htmlqp(), and html5qp() if they haven't been defined already. +- Fix for :nth-child(n+B) to select B-th and all following elements +- Fix for :nth-child(-n+B) to select first B elements +- Update PHPUnit Test Suite to use @dataProvider in testPseudoClassNthChild() to reduce code repetition # 4.0.0 diff --git a/src/CSS/QueryPathEventHandler.php b/src/CSS/QueryPathEventHandler.php index 43e06e3a..ab5b8b70 100644 --- a/src/CSS/QueryPathEventHandler.php +++ b/src/CSS/QueryPathEventHandler.php @@ -734,9 +734,15 @@ protected function parseAnB($rule) throw new ParseException("nth-child value is invalid."); } - // Each of these is legal: 1, -1, and -. '-' is shorthand for -1. + // Each of these is legal: 1, -1, - and . '-' is shorthand for -1. is shorthand for 1 $aVal = trim($rule[0]); - $aVal = ($aVal == '-') ? -1 : (int) $aVal; + if ($aVal === '') { + $aVal = 1; + } elseif ($aVal === '-') { + $aVal = -1; + } else { + $aVal = (int) $aVal; + } $bVal = ! empty($rule[1]) ? (int) trim($rule[1]) : 0; diff --git a/tests/QueryPath/CSS/QueryPathEventHandlerTest.php b/tests/QueryPath/CSS/QueryPathEventHandlerTest.php index fb025230..5443aead 100644 --- a/tests/QueryPath/CSS/QueryPathEventHandlerTest.php +++ b/tests/QueryPath/CSS/QueryPathEventHandlerTest.php @@ -615,7 +615,34 @@ public function testChildAtIndex() { $this->assertEquals('one', $this->nthMatch($matches, 1)->getAttribute('id')); }*/ - public function testPseudoClassNthChild() + public function nthChildProvider(): array + { + return [ + [':root :even', 3, 'four' ], // full list + ['i:even', 2, 'four' ], // restricted to specific element + ['i:odd', 3, 'three' ], // restricted to specific element, odd this time + ['i:nth-child(odd)', 3, 'three' ], // odd + ['i:nth-child(2n+1)', 3, 'three' ], // odd, equiv to 2n + 1 + ['i:nth-child(2n-1)', 3, 'three' ], // odd, equiv to 2n + 1 + ['i:nth-child(2n)', 2, 'four' ], // even + ['i:nth-child(-2n)', 0, null ], // empty list (An+B will only accept positive results and without B this will always be negative) + ['i:nth-child(-2n+4)', 2, 'two', 0 ], // two and four only + ['i:nth-child(-2n+4)', 2, 'four', 1 ], // two and four only + ['i:nth-child(4n)', 1, 'four', 0 ], // every fourth row + ['i:nth-child(4n+1)', 2, 'five' ], // first of every four rows + ['i:nth-child(1)', 1, 'one', 0 ], // first row + ['i:nth-child(0n-0)', 0, null ], // empty list + ['i:nth-child(n+3)', 3, 'four' ], // third+ lines + ['i:nth-child(-n+3)', 3, 'two' ], // first three elements + ['i:nth-child(-n+4)', 4, 'two' ], // first four lines + ['i:nth-child(0n+2)', 1, 'two', 0 ], // second element in a group of siblings + ]; + } + + /** + * @dataProvider nthChildProvider + */ + public function testPseudoClassNthChild($selector, $matchesCount, $matchId, $matchIndex = 1) { $xml = ' @@ -631,95 +658,16 @@ public function testPseudoClassNthChild() // Test full list $handler = new QueryPathEventHandler($doc); - $handler->find(':root :even'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test restricted to specific element - $handler = new QueryPathEventHandler($doc); - $handler->find('i:even'); - $matches = $handler->getMatches(); - $this->assertEquals(2, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test restricted to specific element, odd this time - $handler = new QueryPathEventHandler($doc); - $handler->find('i:odd'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(odd) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(odd)'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(2n+1) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(2n+1)'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(2n) (even) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(2n)'); + $handler->find($selector); $matches = $handler->getMatches(); - $this->assertEquals(2, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Not totally sure what should be returned here - // Test nth-child(-2n) - // $handler = new QueryPathEventHandler($doc); - // $handler->find('i:nth-child(-2n)'); - // $matches = $handler->getMatches(); - // $this->assertEquals(2, $matches->count()); - // $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(2n-1) (odd, equiv to 2n + 1) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(2n-1)'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(4n) (every fourth row) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(4n)'); - $matches = $handler->getMatches(); - $this->assertEquals(1, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 0)->getAttribute('id')); - - // Test nth-child(4n+1) (first of every four rows) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(4n+1)'); - $matches = $handler->getMatches(); - // Should match rows one and five - $this->assertEquals(2, $matches->count()); - $this->assertEquals('five', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(1) (First row) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(1)'); - $matches = $handler->getMatches(); - $this->assertEquals(1, $matches->count()); - $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); - - // Test nth-child(0n-0) (Empty list) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(0n-0)'); - $matches = $handler->getMatches(); - $this->assertEquals(0, $matches->count()); - - // Test nth-child(-n+3) (First three lines) - // $handler = new QueryPathEventHandler($doc); - // $handler->find('i:nth-child(-n+3)'); - // $matches = $handler->getMatches(); - // $this->assertEquals(3, $matches->count()); + $this->assertEquals($matchesCount, $matches->count()); + if ($matchesCount) { + $this->assertEquals($matchId, $this->nthMatch($matches, $matchIndex)->getAttribute('id')); + } + } + public function testPseudoClassNthChildNested() + { $xml = '