Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ docs/*
doc/*
test/fakepear
vendor/
composer.lock
composer.lock
.phpunit.result.cache
.idea/
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
QueryPath Changelog
===========================

# 4.0.2

- 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.1

- Only define global functions qp(), htmlqp(), and html5qp() if they haven't been defined already.
Expand Down
10 changes: 8 additions & 2 deletions src/CSS/QueryPathEventHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <empty>. '-' is shorthand for -1. <empty> 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;

Expand Down
122 changes: 34 additions & 88 deletions tests/QueryPath/CSS/QueryPathEventHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,32 @@ 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)', 2, 'four' ], // Not totally sure what should be returned here
['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 = '<?xml version="1.0" ?>
<test>
Expand All @@ -631,95 +656,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 = '<?xml version="1.0" ?>
<test>
<i class="odd" id="one"/>
Expand Down