diff options
Diffstat (limited to 'lib/querypath/test/Tests/QueryPath/CSS')
7 files changed, 3338 insertions, 0 deletions
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php b/lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php new file mode 100644 index 0000000..9542482 --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php @@ -0,0 +1,357 @@ +<?php +/** + * @file + * CSS Event handling tests + */ +namespace QueryPath\Tests; + +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\Token; +use \QueryPath\CSS\DOMTraverser; +use \QueryPath\CSS\Parser; +use \QueryPath\CSS\EventHandler; + +define('TRAVERSER_XML', __DIR__ . '/../../../DOMTraverserTest.xml'); + +/** + * @ingroup querypath_tests + * @group CSS + */ +class DOMTraverserTest extends TestCase { + + protected $xml_file = TRAVERSER_XML; + public function debug($msg) { + fwrite(STDOUT, PHP_EOL . $msg); + } + + public function testConstructor() { + $dom = new \DOMDocument('1.0'); + $dom->load($this->xml_file); + + $splos = new \SPLObjectStorage(); + $splos->attach($dom); + + $traverser = new DOMTraverser($splos); + + $this->assertInstanceOf('\QueryPath\CSS\Traverser', $traverser); + $this->assertInstanceOf('\QueryPath\CSS\DOMTraverser', $traverser); + } + + protected function traverser() { + $dom = new \DOMDocument('1.0'); + $dom->load($this->xml_file); + + $splos = new \SPLObjectStorage(); + $splos->attach($dom->documentElement); + + $traverser = new DOMTraverser($splos); + + return $traverser; + } + + protected function find($selector) { + return $this->traverser()->find($selector)->matches(); + } + + public function testFind() { + $res = $this->traverser()->find('root'); + + // Ensure that return contract is not violated. + $this->assertInstanceOf('\QueryPath\CSS\Traverser', $res); + } + + public function testMatches() { + $res = $this->traverser()->matches(); + $this->assertEquals(1, count($res)); + } + + public function testMatchElement() { + // Canary: If element does not exist, must return FALSE. + $matches = $this->find('NO_SUCH_ELEMENT'); + $this->assertEquals(0, count($matches)); + + // Test without namespace + $matches = $this->find('root'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('crowded'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('outside'); + $this->assertEquals(3, count($matches)); + + // Check nested elements. + $matches = $this->find('a'); + $this->assertEquals(3, count($matches)); + + // Test wildcard. + $traverser = $this->traverser(); + $matches = $traverser->find('*')->matches(); + $actual= $traverser->getDocument()->getElementsByTagName('*'); + $this->assertEquals($actual->length, count($matches)); + + + // Test with namespace + //$this->markTestIncomplete(); + $matches = $this->find('ns_test'); + $this->assertEquals(3, count($matches)); + + $matches = $this->find('test|ns_test'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('test|ns_test>ns_attr'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('*|ns_test'); + $this->assertEquals(3, count($matches)); + + $matches = $this->find('test|*'); + $this->assertEquals(1, count($matches)); + + // Test where namespace is declared on the element. + $matches = $this->find('newns|my_element'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('test|ns_test>newns|my_element'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('test|*>newns|my_element'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('*|ns_test>newns|my_element'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('*|*>newns|my_element'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('*>newns|my_element'); + $this->assertEquals(1, count($matches)); + } + + public function testMatchAttributes() { + + $matches = $this->find('crowded[attr1]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('crowded[attr1=one]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('crowded[attr2^=tw]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('classtest[class~=two]'); + $this->assertEquals(1, count($matches)); + $matches = $this->find('classtest[class~=one]'); + $this->assertEquals(1, count($matches)); + $matches = $this->find('classtest[class~=seven]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('crowded[attr0]'); + $this->assertEquals(0, count($matches)); + + $matches = $this->find('[level=1]'); + $this->assertEquals(3, count($matches)); + + $matches = $this->find('[attr1]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('[test|myattr]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('[test|myattr=foo]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('[*|myattr=foo]'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('[|myattr=foo]'); + $this->assertEquals(0, count($matches)); + + $matches = $this->find('[|level=1]'); + $this->assertEquals(3, count($matches)); + + $matches = $this->find('[*|level=1]'); + $this->assertEquals(4, count($matches)); + + // Test namespace on attr where namespace + // is declared on that element + $matches = $this->find('[nuther|ping]'); + $this->assertEquals(1, count($matches)); + + // Test multiple namespaces on an element. + $matches = $this->find('[*|ping=3]'); + $this->assertEquals(1, count($matches)); + + // Test multiple namespaces on an element. + $matches = $this->find('[*|ping]'); + $this->assertEquals(1, count($matches)); + } + + public function testMatchId() { + $matches = $this->find('idtest#idtest-one'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('#idtest-one'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('outter#fake'); + $this->assertEquals(0, count($matches)); + + $matches = $this->find('#fake'); + $this->assertEquals(0, count($matches)); + } + + public function testMatchClasses() { + // Basic test. + $matches = $this->find('a.a1'); + $this->assertEquals(1, count($matches)); + + // Count multiple. + $matches = $this->find('.first'); + $this->assertEquals(2, count($matches)); + + // Grab one in the middle of a list. + $matches = $this->find('.four'); + $this->assertEquals(1, count($matches)); + + // One element with two classes. + $matches = $this->find('.three.four'); + $this->assertEquals(1, count($matches)); + } + + public function testMatchPseudoClasses() { + + $matches = $this->find('ul>li:first'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('ul>li:not(.first)'); + $this->assertEquals(5, count($matches)); + + } + + public function testMatchPseudoElements() { + $matches = $this->find('p::first-line'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('p::first-letter'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('p::before'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('p::after'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('bottom::after'); + $this->assertEquals(0, count($matches)); + } + + public function testCombineAdjacent() { + // Simple test + $matches = $this->find('idtest + p'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('p', $m->tagName); + } + + // Test ignoring PCDATA + $matches = $this->find('p + one'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('one', $m->tagName); + } + + // Test that non-adjacent elements don't match. + $matches = $this->find('idtest + one'); + foreach ($matches as $m) { + $this->assertEquals('one', $m->tagName); + } + $this->assertEquals(0, count($matches), 'Non-adjacents should not match.'); + + // Test that elements BEFORE don't match + $matches = $this->find('one + p'); + foreach ($matches as $m) { + $this->assertEquals('one', $m->tagName); + } + $this->assertEquals(0, count($matches), 'Match only if b is after a'); + } + + public function testCombineSibling() { + // Canary: + $matches = $this->find('one ~ two'); + $this->assertEquals(0, count($matches)); + + // Canary 2: + $matches = $this->find('NO_SUCH_ELEMENT ~ two'); + $this->assertEquals(0, count($matches)); + + // Simple test + $matches = $this->find('idtest ~ p'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('p', $m->tagName); + } + + // Simple test + $matches = $this->find('outside ~ p'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('p', $m->tagName); + } + + // Matches only go left, not right. + $matches = $this->find('p ~ outside'); + $this->assertEquals(0, count($matches)); + } + public function testCombineDirectDescendant() { + // Canary: + $matches = $this->find('one > four'); + $this->assertEquals(0, count($matches)); + + $matches = $this->find('two>three'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('three', $m->tagName); + } + + $matches = $this->find('one > two > three'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('three', $m->tagName); + } + + $matches = $this->find('a>a>a'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('a>a'); + $this->assertEquals(2, count($matches)); + } + public function testCombineAnyDescendant() { + // Canary + $matches = $this->find('four one'); + $this->assertEquals(0, count($matches)); + + $matches = $this->find('one two'); + $this->assertEquals(1, count($matches)); + foreach ($matches as $m) { + $this->assertEquals('two', $m->tagName); + } + + $matches = $this->find('one four'); + $this->assertEquals(1, count($matches)); + + $matches = $this->find('a a'); + $this->assertEquals(2, count($matches)); + + $matches = $this->find('root two four'); + $this->assertEquals(1, count($matches)); + } + public function testMultipleSelectors() { + // fprintf(STDOUT, "=========TEST=========\n\n"); + $matches = $this->find('one, two'); + $this->assertEquals(2, count($matches)); + } + +} + diff --git a/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php b/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php new file mode 100644 index 0000000..981ddd1 --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php @@ -0,0 +1,520 @@ +<?php +/** + * @file + * CSS Event handling tests + */ +namespace QueryPath\Tests; + +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\Token; +use \QueryPath\CSS\QueryPathEventHandler; +use \QueryPath\CSS\Parser; +use \QueryPath\CSS\EventHandler; + +/** + * @ingroup querypath_tests + * @group CSS + */ +class ParserTest extends TestCase { + + private function getMockHandler($method) { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array($method)); + $mock->expects($this->once()) + ->method($method) + ->with($this->equalTo('mytest')); + return $mock; + } + + public function testElementID() { + $mock = $this->getMockHandler('elementID'); + $parser = new Parser('#mytest', $mock); + $parser->parse(); + + } + + public function testElement() { + + // Without namespace + $mock = $this->getMockHandler('element'); + $parser = new Parser('mytest', $mock); + $parser->parse(); + + // With empty namespace + $mock = $this->getMockHandler('element'); + $parser = new Parser('|mytest', $mock); + $parser->parse(); + } + + public function testElementNS() { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementNS')); + $mock->expects($this->once()) + ->method('elementNS') + ->with($this->equalTo('mytest'), $this->equalTo('myns')); + + $parser = new Parser('myns|mytest', $mock); + $parser->parse(); + + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementNS')); + $mock->expects($this->once()) + ->method('elementNS') + ->with($this->equalTo('mytest'), $this->equalTo('*')); + + $parser = new Parser('*|mytest', $mock); + $parser->parse(); + + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElementInNS')); + $mock->expects($this->once()) + ->method('anyElementInNS') + ->with($this->equalTo('*')); + + $parser = new Parser('*|*', $mock); + $parser->parse(); + } + + public function testAnyElement() { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElement', 'element')); + $mock->expects($this->once()) + ->method('anyElement'); + + $parser = new Parser('*', $mock); + $parser->parse(); + } + + public function testAnyElementInNS() { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElementInNS', 'element')); + $mock->expects($this->once()) + ->method('anyElementInNS') + ->with($this->equalTo('myns')); + + $parser = new Parser('myns|*', $mock); + $parser->parse(); + } + + public function testElementClass() { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementClass')); + $mock->expects($this->once()) + ->method('elementClass') + ->with($this->equalTo('myclass')); + + $parser = new Parser('.myclass', $mock); + $parser->parse(); + } + + public function testPseudoClass() { + + // Test empty pseudoclass + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass')); + $mock->expects($this->once()) + ->method('pseudoClass') + ->with($this->equalTo('mypclass')); + + $parser = new Parser('myele:mypclass', $mock); + $parser->parse(); + + // Test pseudoclass with value + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass')); + $mock->expects($this->once()) + ->method('pseudoClass') + ->with($this->equalTo('mypclass'), $this->equalTo('myval')); + + $parser = new Parser('myele:mypclass(myval)', $mock); + $parser->parse(); + + // Test pseudclass with pseudoclass: + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass')); + $mock->expects($this->once()) + ->method('pseudoClass') + ->with($this->equalTo('mypclass'), $this->equalTo(':anotherPseudo')); + + $parser = new Parser('myele:mypclass(:anotherPseudo)', $mock); + $parser->parse(); + + } + + public function testPseudoElement() { + // Test pseudo-element + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoElement')); + $mock->expects($this->once()) + ->method('pseudoElement') + ->with($this->equalTo('mypele')); + + $parser = new Parser('myele::mypele', $mock); + $parser->parse(); + } + + public function testDirectDescendant() { + // Test direct Descendant + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('directDescendant')); + $mock->expects($this->once()) + ->method('directDescendant'); + + $parser = new Parser('ele1 > ele2', $mock); + $parser->parse(); + + } + + public function testAnyDescendant() { + // Test direct Descendant + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyDescendant')); + $mock->expects($this->once()) + ->method('anyDescendant'); + + $parser = new Parser('ele1 .class', $mock); + $parser->parse(); + + } + + public function testAdjacent() { + // Test sibling + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('adjacent')); + $mock->expects($this->once()) + ->method('adjacent'); + + $parser = new Parser('ele1 + ele2', $mock); + $parser->parse(); + } + + public function testSibling() { + // Test adjacent + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('sibling')); + $mock->expects($this->once()) + ->method('sibling'); + + $parser = new Parser('ele1 ~ ele2', $mock); + $parser->parse(); + } + + public function testAnotherSelector() { + // Test adjacent + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anotherSelector')); + $mock->expects($this->once()) + ->method('anotherSelector'); + + $parser = new Parser('ele1 , ele2', $mock); + $parser->parse(); + } + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testIllegalAttribute() { + + // Note that this is designed to test throwError() as well as + // bad selector handling. + + $parser = new Parser('[test=~far]', new TestEventHandler()); + try { + $parser->parse(); + } + catch (Exception $e) { + //print $e->getMessage(); + throw $e; + } + } + + public function testAttribute() { + $selectors = array( + 'element[attr]' => 'attr', + '*[attr]' => 'attr', + 'element[attr]:class' => 'attr', + 'element[attr2]' => 'attr2', // Issue # + ); + foreach ($selectors as $filter => $expected) { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attribute')); + $mock->expects($this->once()) + ->method('attribute') + ->with($this->equalTo($expected)); + + $parser = new Parser($filter, $mock); + $parser->parse(); + } + + $selectors = array( + '*[attr="value"]' => array('attr','value',EventHandler::isExactly), + '*[attr^="value"]' => array('attr','value',EventHandler::beginsWith), + '*[attr$="value"]' => array('attr','value',EventHandler::endsWith), + '*[attr*="value"]' => array('attr','value',EventHandler::containsInString), + '*[attr~="value"]' => array('attr','value',EventHandler::containsWithSpace), + '*[attr|="value"]' => array('attr','value',EventHandler::containsWithHyphen), + + // This should act like [attr="value"] + '*[|attr="value"]' => array('attr', 'value', EventHandler::isExactly), + + // This behavior is displayed in the spec, but not accounted for in the + // grammar: + '*[attr=value]' => array('attr','value',EventHandler::isExactly), + + // Should be able to escape chars using backslash. + '*[attr="\.value"]' => array('attr','.value',EventHandler::isExactly), + '*[attr="\.\]\]\]"]' => array('attr','.]]]',EventHandler::isExactly), + + // Backslash-backslash should resolve to single backslash. + '*[attr="\\\c"]' => array('attr','\\c',EventHandler::isExactly), + + // Should return an empty value. It seems, though, that a value should be + // passed here. + '*[attr=""]' => array('attr','',EventHandler::isExactly), + ); + foreach ($selectors as $filter => $expected) { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attribute')); + $mock->expects($this->once()) + ->method('attribute') + ->with($this->equalTo($expected[0]), $this->equalTo($expected[1]), $this->equalTo($expected[2])); + + $parser = new Parser($filter, $mock); + $parser->parse(); + } + } + + public function testAttributeNS() { + $selectors = array( + '*[ns|attr="value"]' => array('attr', 'ns', 'value',EventHandler::isExactly), + '*[*|attr^="value"]' => array('attr', '*', 'value',EventHandler::beginsWith), + '*[*|attr|="value"]' => array('attr', '*', 'value',EventHandler::containsWithHyphen), + ); + + foreach ($selectors as $filter => $expected) { + $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attributeNS')); + $mock->expects($this->once()) + ->method('attributeNS') + ->with($this->equalTo($expected[0]), $this->equalTo($expected[1]), $this->equalTo($expected[2]), $this->equalTo($expected[3])); + + $parser = new Parser($filter, $mock); + $parser->parse(); + } + } + + // Test things that should break... + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testIllegalCombinators1() { + $handler = new TestEventHandler(); + $parser = new Parser('ele1 > > ele2', $handler); + $parser->parse(); + } + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testIllegalCombinators2() { + $handler = new TestEventHandler(); + $parser = new Parser('ele1+ ,ele2', $handler); + $parser->parse(); + } + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testIllegalID() { + $handler = new TestEventHandler(); + $parser = new Parser('##ID', $handler); + $parser->parse(); + } + + // Test combinations + + public function testElementNSClassAndAttribute() { + + $expect = array( + new TestEvent(TestEvent::elementNS, 'element', 'ns'), + new TestEvent(TestEvent::elementClass, 'class'), + new TestEvent(TestEvent::attribute, 'name', 'value', EventHandler::isExactly), + ); + $selector = 'ns|element.class[name="value"]'; + + $handler = new TestEventHandler(); + $handler->expects($expect); + $parser = new Parser($selector, $handler); + $parser->parse(); + $this->assertTrue($handler->success()); + + // Again, with spaces this time: + $selector = ' ns|element. class[ name = "value" ]'; + + $handler = new TestEventHandler(); + $handler->expects($expect); + $parser = new Parser($selector, $handler); + $parser->parse(); + + //$handler->dumpStack(); + $this->assertTrue($handler->success()); + } + + public function testAllCombo() { + + $selector = '*|ele1 > ele2.class1 + ns1|ele3.class2[attr=simple] ~ + .class2[attr2~="longer string of text."]:pseudoClass(value) + .class3::pseudoElement'; + $expect = array( + new TestEvent(TestEvent::elementNS, 'ele1', '*'), + new TestEvent(TestEvent::directDescendant), + new TestEvent(TestEvent::element, 'ele2'), + new TestEvent(TestEvent::elementClass, 'class1'), + new TestEvent(TestEvent::adjacent), + new TestEvent(TestEvent::elementNS, 'ele3', 'ns1'), + new TestEvent(TestEvent::elementClass, 'class2'), + new TestEvent(TestEvent::attribute, 'attr', 'simple', EventHandler::isExactly), + new TestEvent(TestEvent::sibling), + new TestEvent(TestEvent::elementClass, 'class2'), + new TestEvent(TestEvent::attribute, 'attr2', 'longer string of text.', EventHandler::containsWithSpace), + new TestEvent(TestEvent::pseudoClass, 'pseudoClass', 'value'), + new TestEvent(TestEvent::anyDescendant), + new TestEvent(TestEvent::elementClass, 'class3'), + new TestEvent(TestEvent::pseudoElement, 'pseudoElement'), + ); + + + $handler = new TestEventHandler(); + $handler->expects($expect); + $parser = new Parser($selector, $handler); + $parser->parse(); + + //$handler->dumpStack(); + + $this->assertTrue($handler->success()); + + /* + // Again, with spaces this time: + $selector = ' *|ele1 > ele2. class1 + ns1|ele3. class2[ attr=simple] ~ .class2[attr2 ~= "longer string of text."]:pseudoClass(value) .class3::pseudoElement'; + + $handler = new TestEventHandler(); + $handler->expects($expect); + $parser = new Parser($selector, $handler); + $parser->parse(); + + $handler->dumpStack(); + $this->assertTrue($handler->success()); + */ + } +} + +/** + * Testing harness for the EventHandler. + * + * @ingroup querypath_tests + * @group CSS + */ +class TestEventHandler implements EventHandler { + var $stack = NULL; + var $expect = array(); + + public function __construct() { + $this->stack = array(); + } + + public function getStack() { + return $this->stack; + } + + public function dumpStack() { + print "\nExpected:\n"; + $format = "Element %d: %s\n"; + foreach ($this->expect as $item) { + printf($format, $item->eventType(), implode(',', $item->params())); + } + + print "Got:\n"; + foreach($this->stack as $item){ + printf($format, $item->eventType(), implode(',', $item->params())); + } + } + + public function expects($stack) { + $this->expect = $stack; + } + + public function success() { + return ($this->expect == $this->stack); + } + + public function elementID($id) { + $this->stack[] = new TestEvent(TestEvent::elementID, $id); + } + public function element($name) { + $this->stack[] = new TestEvent(TestEvent::element, $name); + } + public function elementNS($name, $namespace = NULL){ + $this->stack[] = new TestEvent(TestEvent::elementNS, $name, $namespace); + } + public function anyElement(){ + $this->stack[] = new TestEvent(TestEvent::anyElement); + } + public function anyElementInNS($ns){ + $this->stack[] = new TestEvent(TestEvent::anyElementInNS, $ns); + } + public function elementClass($name){ + $this->stack[] = new TestEvent(TestEvent::elementClass, $name); + } + public function attribute($name, $value = NULL, $operation = EventHandler::isExactly){ + $this->stack[] = new TestEvent(TestEvent::attribute, $name, $value, $operation); + } + public function attributeNS($name, $ns, $value = NULL, $operation = EventHandler::isExactly){ + $this->stack[] = new TestEvent(TestEvent::attributeNS, $name, $ns, $value, $operation); + } + public function pseudoClass($name, $value = NULL){ + $this->stack[] = new TestEvent(TestEvent::pseudoClass, $name, $value); + } + public function pseudoElement($name){ + $this->stack[] = new TestEvent(TestEvent::pseudoElement, $name); + } + public function directDescendant(){ + $this->stack[] = new TestEvent(TestEvent::directDescendant); + } + public function anyDescendant() { + $this->stack[] = new TestEvent(TestEvent::anyDescendant); + } + public function adjacent(){ + $this->stack[] = new TestEvent(TestEvent::adjacent); + } + public function anotherSelector(){ + $this->stack[] = new TestEvent(TestEvent::anotherSelector); + } + public function sibling(){ + $this->stack[] = new TestEvent(TestEvent::sibling); + } +} + +/** + * Simple utility object for use with the TestEventHandler. + * + * @ingroup querypath_tests + * @group CSS + */ +class TestEvent { + const elementID = 0; + const element = 1; + const elementNS = 2; + const anyElement = 3; + const elementClass = 4; + const attribute = 5; + const attributeNS = 6; + const pseudoClass = 7; + const pseudoElement = 8; + const directDescendant = 9; + const adjacent = 10; + const anotherSelector = 11; + const sibling = 12; + const anyElementInNS = 13; + const anyDescendant = 14; + + var $type = NULL; + var $params = NULL; + + public function __construct($event_type) { + $this->type = $event_type; + $args = func_get_args(); + array_shift($args); + $this->params = $args; + } + + public function eventType() { + return $this->type; + } + + public function params() { + return $this->params; + } +} diff --git a/lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php b/lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php new file mode 100644 index 0000000..6798b1c --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php @@ -0,0 +1,828 @@ +<?php +/** + * @file + * CSS Event handling tests for PseudoClasses. + */ +namespace QueryPath\Tests; + +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\DOMTraverser\PseudoClass; + +/** + * @ingroup querypath_tests + * @group CSS + */ +class PseudoClassTest extends TestCase { + + protected function doc($string, $tagname) { + + $doc = new \DOMDocument('1.0'); + $doc->loadXML($string); + + $found = $doc->getElementsByTagName($tagname)->item(0); + + return array($found, $doc->documentElement); + + } + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testUnknownPseudoClass() { + $xml = '<?xml version="1.0"?><root><foo>test</foo></root>'; + + list($ele, $root) = $this->doc($xml, 'foo'); + $ps = new PseudoClass(); + + $ps->elementMatches('TotallyFake', $ele, $root); + } + + public function testLang() { + $xml = '<?xml version="1.0"?><root><foo lang="en-US">test</foo></root>'; + + list($ele, $root) = $this->doc($xml, 'foo'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('lang', $ele, $root, 'en-US'); + $this->assertTrue($ret); + $ret = $ps->elementMatches('lang', $ele, $root, 'en'); + $this->assertTrue($ret); + $ret = $ps->elementMatches('lang', $ele, $root, 'fr-FR'); + $this->assertFalse($ret); + $ret = $ps->elementMatches('lang', $ele, $root, 'fr'); + $this->assertFalse($ret); + + + // Check on ele that doesn't have lang. + $ret = $ps->elementMatches('lang', $root, $root, 'fr'); + $this->assertFalse($ret); + + } + + public function testLangNS() { + $xml = '<?xml version="1.0"?><root><foo xml:lang="en-US">test</foo></root>'; + + list($ele, $root) = $this->doc($xml, 'foo'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('lang', $ele, $root, 'en-US'); + $this->assertTrue($ret); + $ret = $ps->elementMatches('lang', $ele, $root, 'en'); + $this->assertTrue($ret); + $ret = $ps->elementMatches('lang', $ele, $root, 'fr-FR'); + $this->assertFalse($ret); + $ret = $ps->elementMatches('lang', $ele, $root, 'fr'); + $this->assertFalse($ret); + + + // Check on ele that doesn't have lang. + $ret = $ps->elementMatches('lang', $root, $root, 'fr'); + $this->assertFalse($ret); + } + + public function testFormType() { + $xml = '<?xml version="1.0"?><root><foo type="submit">test</foo></root>'; + + list($ele, $root) = $this->doc($xml, 'foo'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('submit', $ele, $root); + $this->assertTrue($ret); + + $ret = $ps->elementMatches('reset', $ele, $root); + $this->assertFalse($ret); + + } + + public function testHasAttribute() { + $xml = '<?xml version="1.0"?><root><foo enabled="enabled">test</foo></root>'; + + list($ele, $root) = $this->doc($xml, 'foo'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('enabled', $ele, $root); + $this->assertTrue($ret); + $ret = $ps->elementMatches('disabled', $ele, $root); + $this->assertFalse($ret); + } + + public function testHeader() { + $xml = '<?xml version="1.0"?><root><h1>TEST</h1><H6></H6><hi/><h12/><h1i/></root>'; + + list($ele, $root) = $this->doc($xml, 'h1'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('header', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'H6'); + $ret = $ps->elementMatches('header', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'hi'); + $ret = $ps->elementMatches('header', $ele, $root); + $this->assertFalse($ret); + list($ele, $root) = $this->doc($xml, 'h1i'); + $ret = $ps->elementMatches('header', $ele, $root); + $this->assertFalse($ret); + list($ele, $root) = $this->doc($xml, 'h12'); + $ret = $ps->elementMatches('header', $ele, $root); + $this->assertFalse($ret); + } + + public function testContains(){ + $xml = '<?xml version="1.0"?><root><h>This is a test of :contains.</h></root>'; + + list($ele, $root) = $this->doc($xml, 'h'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('contains', $ele, $root, 'test'); + $this->assertTrue($ret); + + $ret = $ps->elementMatches('contains', $ele, $root, 'is a test'); + $this->assertTrue($ret); + + $ret = $ps->elementMatches('contains', $ele, $root, 'This is a test of :contains.'); + $this->assertTrue($ret); + + $ret = $ps->elementMatches('contains', $ele, $root, 'Agent P, here is your mission.'); + $this->assertFalse($ret); + + $ret = $ps->elementMatches('contains', $ele, $root, "'Agent P, here is your mission.'"); + $this->assertFalse($ret); + + } + public function testContainsExactly() { + $xml = '<?xml version="1.0"?><root><h>This is a test of :contains-exactly.</h></root>'; + + list($ele, $root) = $this->doc($xml, 'h'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('contains-exactly', $ele, $root, 'test'); + $this->assertFalse($ret); + + $ret = $ps->elementMatches('contains-exactly', $ele, $root, 'This is a test of :contains-exactly.'); + $this->assertTrue($ret); + + $ret = $ps->elementMatches('contains-exactly', $ele, $root, 'Agent P, here is your mission.'); + $this->assertFalse($ret); + + $ret = $ps->elementMatches('contains-exactly', $ele, $root, '"Agent P, here is your mission."'); + $this->assertFalse($ret); + } + public function testHas() { + $xml = '<?xml version="1.0"?><root><button disabled="disabled"/></root>'; + + list($ele, $root) = $this->doc($xml, 'button'); + $ps = new PseudoClass(); + + // Example from CSS 4 spec + $ret = $ps->elementMatches('matches', $ele, $root, '[disabled]'); + $this->assertTrue($ret); + + // Regression for Issue #84: + $xml = '<?xml version="1.0"?><root><a/><a/><a src="/foo/bar"/><b src="http://example.com/foo/bar"/></root>'; + + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $ele->childNodes; + $ps = new PseudoClass(); + + $i = 0; + foreach ($nl as $n) { + $ret = $ps->elementMatches('matches', $n, $root, '[src^="/foo/"]'); + if ($ret) { + ++$i; + } + } + $this->assertEquals(1, $i); + } + public function testParent() { + $ps = new PseudoClass(); + + $xml = '<?xml version="1.0"?><root><p/></root>'; + list($ele, $root) = $this->doc($xml, 'p'); + $ret = $ps->elementMatches('parent', $ele, $root); + $this->assertFalse($ret); + + $xml = '<?xml version="1.0"?><root><p></p>></root>'; + list($ele, $root) = $this->doc($xml, 'p'); + $ret = $ps->elementMatches('parent', $ele, $root); + $this->assertFalse($ret); + + $xml = '<?xml version="1.0"?><root><p>TEST</p></root>'; + list($ele, $root) = $this->doc($xml, 'p'); + $ret = $ps->elementMatches('parent', $ele, $root); + $this->assertTrue($ret); + + $xml = '<?xml version="1.0"?><root><p><q/></p></root>'; + list($ele, $root) = $this->doc($xml, 'p'); + $ret = $ps->elementMatches('parent', $ele, $root); + $this->assertTrue($ret); + + } + public function testFirst() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><p><q/></p><a></a><b/></root>'; + + list($ele, $root) = $this->doc($xml, 'q'); + $ret = $ps->elementMatches('first', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'p'); + $ret = $ps->elementMatches('first', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'b'); + $ret = $ps->elementMatches('first', $ele, $root); + $this->assertFalse($ret); + + } + public function testLast() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><p><q/></p><a></a><b/></root>'; + + list($ele, $root) = $this->doc($xml, 'q'); + $ret = $ps->elementMatches('last', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'p'); + $ret = $ps->elementMatches('last', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'b'); + $ret = $ps->elementMatches('last', $ele, $root); + $this->assertTrue($ret); + } + public function testNot() { + $xml = '<?xml version="1.0"?><root><button/></root>'; + + list($ele, $root) = $this->doc($xml, 'button'); + $ps = new PseudoClass(); + + // Example from CSS 4 spec + $ret = $ps->elementMatches('not', $ele, $root, '[disabled]'); + $this->assertTrue($ret); + + $xml = '<?xml version="1.0"?><root><b/><b/><c/><b/></root>'; + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $ele->childNodes; + + $i = 0; + foreach ($nl as $n) { + $ret = $ps->elementMatches('not', $n, $root, 'c'); + if ($ret) { + ++$i; + } + } + $this->assertEquals(3, $i); + + } + public function testEmpty() { + $xml = '<?xml version="1.0"?><root><foo lang="en-US">test</foo><bar/><baz></baz></root>'; + + list($ele, $root) = $this->doc($xml, 'foo'); + $ps = new PseudoClass(); + + $ret = $ps->elementMatches('empty', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'bar'); + $ret = $ps->elementMatches('empty', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'baz'); + $ret = $ps->elementMatches('empty', $ele, $root); + $this->assertTrue($ret); + } + public function testOnlyChild() { + $xml = '<?xml version="1.0"?><root><foo>test<a/></foo><b><c/></b></root>'; + $ps = new PseudoClass(); + + list($ele, $root) = $this->doc($xml, 'foo'); + $ret = $ps->elementMatches('only-child', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('only-child', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'c'); + $ret = $ps->elementMatches('only-child', $ele, $root); + $this->assertTrue($ret); + } + public function testLastOfType() { + $xml = '<?xml version="1.0"?><root><one><a/><b/><c/></one><two><d/><d/><b/></two></root>'; + $ps = new PseudoClass(); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('last-of-type', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $nl = $root->getElementsByTagName('d'); + + $ret = $ps->elementMatches('last-of-type', $nl->item(0), $root); + $this->assertFalse($ret); + + $ret = $ps->elementMatches('last-of-type', $nl->item(1), $root); + $this->assertTrue($ret); + } + public function testFirstOftype() { + $xml = '<?xml version="1.0"?><root><one><a/><b/><c/></one><two><d/><d/><b/></two></root>'; + $ps = new PseudoClass(); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('first-of-type', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $nl = $root->getElementsByTagName('d'); + + $ret = $ps->elementMatches('first-of-type', $nl->item(0), $root); + $this->assertTrue($ret); + + $ret = $ps->elementMatches('first-of-type', $nl->item(1), $root); + $this->assertFalse($ret); + } + public function testOnlyOfType() { + $xml = '<?xml version="1.0"?><root><one><a/><b/><c/></one><two><d/><d/><b/></two></root>'; + $ps = new PseudoClass(); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('only-of-type', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $nl = $root->getElementsByTagName('d'); + + $ret = $ps->elementMatches('only-of-type', $nl->item(0), $root); + $this->assertFalse($ret); + + $ret = $ps->elementMatches('only-of-type', $nl->item(1), $root); + $this->assertFalse($ret); + } + public function testNthLastChild() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><b/><c/><d/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // 2n + 1 -- Every odd row, from the last element. + $i = 0; + $expects = array('b', 'd'); + $j = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth-last-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.'); + + // 3 (0n+3) -- third from the end (b). + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth-last-child', $n, $root, '3'); + if ($res) { + ++$i; + $this->assertEquals('b', $n->tagName); + } + } + $this->assertEquals(1, $i); + + // -n+3: Last three elements. + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth-last-child', $n, $root, '-n+3'); + if ($res) { + ++$i; + } + } + $this->assertEquals(3, $i); + } + public function testNthChild() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><b/><c/><d/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // 2n + 1 -- Every odd row. + $i = 0; + $expects = array('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 = array('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 = array('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 = array('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; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth-child', $n, $root, '4n-1'); + 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; + } + } + $this->assertEquals(0, $i); + + } + public function testEven() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><b/><c/><d/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + $i = 0; + $expects = array('b', 'd'); + foreach ($nl as $n) { + $res = $ps->elementMatches('even', $n, $root); + if ($res) { + ++$i; + $name = $n->tagName; + $this->assertContains($name, $expects, 'Expected a or c, got ' . $name); + } + } + $this->assertEquals(10, $i, ' even is ten items.'); + } + public function testOdd() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><b/><c/><d/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // Odd + $i = 0; + $expects = array('a', 'c'); + $j = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('odd', $n, $root); + 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, 'Ten odds.'); + } + public function testNthOfTypeChild() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><a/><a/><a/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // Odd + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth-of-type', $n, $root, '2n+1'); + if ($res) { + ++$i; + $name = $n->tagName; + $this->assertEquals('a', $name); + } + } + $this->assertEquals(10, $i, 'Ten odds.'); + + // Fun with ambiguous pseudoclasses: + // This emulates the selector 'root > :nth-of-type(2n+1)' + $xml = '<?xml version="1.0"?><root>'; + $xml .= '<a/><b/><c/><a/><a/><a/>'; + $xml .= '</root>'; + + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // Odd + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth-of-type', $n, $root, '2n+1'); + if ($res) { + ++$i; + $name = $n->tagName; + } + } + // THis should be: 2 x a + 1 x b + 1 x c = 4 + $this->assertEquals(4, $i, 'Four odds.'); + } + public function testNthLastOfTypeChild() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= '<a/><a/><OOPS/><a/><a/>'; + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // Third from beginning is second from last. + $third = $nl->item(2); + $res = $ps->elementMatches('nth-last-of-type', $third, $root, '3'); + $this->assertFalse($res); + + $first = $nl->item(0); + $res = $ps->elementMatches('nth-last-of-type', $first, $root, '3'); + $this->assertFalse($res); + + $last = $nl->item(3); + $res = $ps->elementMatches('nth-last-of-type', $last, $root, '3'); + $this->assertFalse($res); + + // Second from start is 3rd from last + $second = $nl->item(1); + $res = $ps->elementMatches('nth-last-of-type', $second, $root, '3'); + $this->assertTrue($res); + + } + public function testLink() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><a href="foo"><b hreff="bar">test</b></a><c/></root>'; + + list($ele, $root) = $this->doc($xml, 'c'); + $ret = $ps->elementMatches('link', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('link', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'b'); + $ret = $ps->elementMatches('link', $ele, $root); + $this->assertFalse($ret); + } + public function testRoot() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><p><q/></p><a></a><b/></root>'; + + list($ele, $root) = $this->doc($xml, 'q'); + $ret = $ps->elementMatches('root', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'root'); + $ret = $ps->elementMatches('root', $ele, $root); + $this->assertTrue($ret); + } + /* Deprecated and removed. + public function testXRoot() { + } + public function testXReset() { + } + */ + public function testLt() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><a/><a/><a/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // Odd + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('lt', $n, $root, '15'); + if ($res) { + ++$i; + $name = $n->tagName; + } + } + $this->assertEquals(15, $i, 'Less than or equal to 15.'); + } + public function testGt() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><a/><a/><a/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + // Odd + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('gt', $n, $root, '15'); + if ($res) { + ++$i; + $name = $n->tagName; + } + } + $this->assertEquals(5, $i, 'Greater than the 15th element.'); + } + public function testEq() { + $xml = '<?xml version="1.0"?><root>'; + $xml .= str_repeat('<a/><b/><c/><a/>', 5); + $xml .= '</root>'; + + $ps = new PseudoClass(); + list($ele, $root) = $this->doc($xml, 'root'); + $nl = $root->childNodes; + + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('eq', $n, $root, '15'); + if ($res) { + ++$i; + $name = $n->tagName; + $this->assertEquals('c', $name); + } + } + $this->assertEquals(1, $i, 'The 15th element.'); + + $i = 0; + foreach ($nl as $n) { + $res = $ps->elementMatches('nth', $n, $root, '15'); + if ($res) { + ++$i; + $name = $n->tagName; + $this->assertEquals('c', $name); + } + } + $this->assertEquals(1, $i, 'The 15th element.'); + } + + public function testAnyLink() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><a href="foo"><b hreff="bar">test</b></a><c/><d src="foo"/></root>'; + + list($ele, $root) = $this->doc($xml, 'c'); + $ret = $ps->elementMatches('any-link', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('any-link', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'd'); + $ret = $ps->elementMatches('any-link', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'b'); + $ret = $ps->elementMatches('any-link', $ele, $root); + $this->assertFalse($ret); + } + public function testLocalLink() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><a href="foo"><b href="http://example.com/bar">test</b></a><c/><d href="file://foo"/></root>'; + + list($ele, $root) = $this->doc($xml, 'c'); + $ret = $ps->elementMatches('local-link', $ele, $root); + $this->assertFalse($ret); + + list($ele, $root) = $this->doc($xml, 'a'); + $ret = $ps->elementMatches('local-link', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'd'); + $ret = $ps->elementMatches('local-link', $ele, $root); + $this->assertTrue($ret); + + list($ele, $root) = $this->doc($xml, 'b'); + $ret = $ps->elementMatches('local-link', $ele, $root); + $this->assertFalse($ret); + } + public function testScope() { + $ps = new PseudoClass(); + $xml = '<?xml version="1.0"?><root><a href="foo"><b>test</b></a></root>'; + + list($ele, $root) = $this->doc($xml, 'a'); + $node = $ele->childNodes->item(0); + $ret = $ps->elementMatches('scope', $node, $ele); + $this->assertFalse($ret); + } +} diff --git a/lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php b/lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php new file mode 100644 index 0000000..01af591 --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php @@ -0,0 +1,1439 @@ +<?php +/** @file + * CSS Event handling tests + */ +namespace QueryPath\Tests; + +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\Token; +use \QueryPath\CSS\QueryPathEventHandler; +use \QueryPath\CSS\Parser; +use \QueryPath\CSS\EventHandler; + +/** + * Tests for QueryPathEventHandler class. + * @ingroup querypath_tests + * @group deprecated + */ +class QueryPathEventHandlerTest extends TestCase { + + + var $xml = '<?xml version="1.0" ?> + <html> + <head> + <title>This is the title</title> + </head> + <body> + <div id="one"> + <div id="two" class="class-one"> + <div id="three"> + Inner text. + </div> + </div> + </div> + <span class="class-two">Nada</span> + <p><p><p><p><p><p><p class="Odd"><p>8</p></p></p></p></p></p></p></p> + <ul> + <li class="Odd" id="li-one">Odd</li> + <li class="even" id="li-two">Even</li> + <li class="Odd" id="li-three">Odd</li> + <li class="even" id="li-four">Even</li> + <li class="Odd" id="li-five">Odd</li> + <li class="even" id="li-six">Even</li> + <li class="Odd" id="li-seven">Odd</li> + <li class="even" id="li-eight">Even</li> + <li class="Odd" id="li-nine">Odd</li> + <li class="even" id="li-ten">Even</li> + </ul> + </body> + </html> + '; + + private function firstMatch($matches) { + $matches->rewind(); + return $matches->current(); + } + private function nthMatch($matches, $n = 0) { + foreach ($matches as $m) { + if ($matches->key() == $n) return $m; + } + } + + public function testGetMatches() { + // Test root element: + $xml = '<?xml version="1.0" ?><test><inside/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test handing it a DOM Document + $handler = new QueryPathEventHandler($doc); + $matches = $handler->getMatches(); + $this->assertTrue($matches->count() == 1); + $match = $this->firstMatch($matches); + $this->assertEquals('test', $match->tagName); + + // Test handling single element + $root = $doc->documentElement; + $handler = new QueryPathEventHandler($root); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('test', $match->tagName); + + // Test handling a node list + $eles = $doc->getElementsByTagName('inside'); + $handler = new QueryPathEventHandler($eles); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('inside', $match->tagName); + + // Test handling an array of elements + $eles = $doc->getElementsByTagName('inside'); + $array = array(); + foreach ($eles as $ele) $array[] = $ele; + $handler = new QueryPathEventHandler($array); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('inside', $match->tagName); + } + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testEmptySelector() { + $xml = '<?xml version="1.0" ?><t:test xmlns:t="urn:foo/bar"><t:inside id="first"/>Text<t:inside/><inside/></t:test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Basic test + $handler = new QueryPathEventHandler($doc); + $handler->find(''); + $matches = $handler->getMatches(); + $this->assertEquals(0, $matches->count()); + } + + public function testElementNS() { + $xml = '<?xml version="1.0" ?><t:test xmlns:t="urn:foo/bar"><t:inside id="first"/>Text<t:inside/><inside/></t:test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Basic test + $handler = new QueryPathEventHandler($doc); + $handler->find('t|inside'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('t:inside', $match->tagName); + + // Basic test + $handler = new QueryPathEventHandler($doc); + $handler->find('t|test'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('t:test', $match->tagName); + } + + + /** + * @expectedException \QueryPath\CSS\ParseException + */ + public function testFailedElementNS() { + $xml = '<?xml version="1.0" ?><t:test xmlns:t="urn:foo/bar"><t:inside id="first"/>Text<t:inside/><inside/></t:test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('myns\:mytest'); + } + + public function testElement() { + $xml = '<?xml version="1.0" ?><test><inside id="first"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Basic test + $handler = new QueryPathEventHandler($doc); + $handler->find('inside'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('inside', $match->tagName); + + $doc = new \DomDocument(); + $doc->loadXML($this->xml); + + // Test getting nested + $handler = new QueryPathEventHandler($doc); + $handler->find('div'); + $matches = $handler->getMatches(); + $this->assertEquals(3, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('div', $match->tagName); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test getting a list + $handler = new QueryPathEventHandler($doc); + $handler->find('li'); + $matches = $handler->getMatches(); + $this->assertEquals(10, $matches->count()); + $match = $this->firstMatch($matches); + //$this->assertEquals('div', $match->tagName); + $this->assertEquals('li-one', $match->getAttribute('id')); + + // Test getting the root element + $handler = new QueryPathEventHandler($doc); + $handler->find('html'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('html', $match->tagName); + } + + public function testElementId() { + // Test root element: + $xml = '<?xml version="1.0" ?><test><inside id="first"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('#first'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('inside', $match->tagName); + + // Test a search with restricted scope: + $handler = new QueryPathEventHandler($doc); + $handler->find('inside#first'); + $matches = $handler->getMatches(); + + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('inside', $match->tagName); + + } + + public function testAnyElementInNS() { + $xml = '<?xml version="1.0" ?><ns1:test xmlns:ns1="urn:foo/bar"><ns1:inside/>Text<ns1:inside/></ns1:test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test handing it a DOM Document + $handler = new QueryPathEventHandler($doc); + $handler->find('ns1|*'); + $matches = $handler->getMatches(); + + $this->assertEquals(3, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('ns1:test', $match->tagName); + + // Test Issue #30: + $xml = '<?xml version="1.0" ?> + <ns1:test xmlns:ns1="urn:foo/bar"> + <ns1:inside> + <ns1:insideInside>Test</ns1:insideInside> + </ns1:inside> + </ns1:test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('ns1|test>*'); + $matches = $handler->getMatches(); + + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('ns1:inside', $match->tagName); + } + + public function testAnyElement() { + $xml = '<?xml version="1.0" ?><test><inside/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test handing it a DOM Document + $handler = new QueryPathEventHandler($doc); + $handler->find('*'); + $matches = $handler->getMatches(); + + $this->assertEquals(3, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('test', $match->tagName); + + $doc = new \DomDocument(); + $doc->loadXML($this->xml); + + // Test handing it a DOM Document + $handler = new QueryPathEventHandler($doc); + $handler->find('#two *'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('three', $match->getAttribute('id')); + + // Regression for issue #30 + $handler = new QueryPathEventHandler($doc); + $handler->find('#one>*'); + $matches = $handler->getMatches(); + + $this->assertEquals(1, $matches->count(), 'Should match just top div.'); + $match = $this->firstMatch($matches); + $this->assertEquals('two', $match->getAttribute('id'), 'Should match ID #two'); + } + + public function testElementClass() { + $xml = '<?xml version="1.0" ?><test><inside class="foo" id="one"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test basic class + $handler = new QueryPathEventHandler($doc); + $handler->find('.foo'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test class in element + $doc = new \DomDocument(); + $doc->loadXML($this->xml); + $handler = new QueryPathEventHandler($doc); + $handler->find('li.Odd'); + $matches = $handler->getMatches(); + $this->assertEquals(5, $matches->count()); + $match = $this->nthMatch($matches, 4); + $this->assertEquals('li-nine', $match->getAttribute('id')); + + // Test ID/class combo + $handler = new QueryPathEventHandler($doc); + $handler->find('.Odd#li-nine'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('li-nine', $match->getAttribute('id')); + + } + + public function testDirectDescendant() { + $xml = '<?xml version="1.0" ?> + <test> + <inside class="foo" id="one"/> + Text + <inside id="two"> + <inside id="inner-one"/> + </inside> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test direct descendent + $handler = new QueryPathEventHandler($doc); + $handler->find('test > inside'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->nthMatch($matches, 1); + $this->assertEquals('two', $match->getAttribute('id')); + + } + + public function testAttribute() { + $xml = '<?xml version="1.0" ?><test><inside id="one" name="antidisestablishmentarianism"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test match on attr name + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test broken form + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[@name]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test match on attr name and equals value + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name="antidisestablishmentarianism"]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test match on containsInString + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name*="disestablish"]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test match on beginsWith + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name^="anti"]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test match on endsWith + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name$="ism"]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test containsWithSpace + $xml = '<?xml version="1.0" ?><test><inside id="one" name="anti dis establishment arian ism"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name~="dis"]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + // Test containsWithHyphen + $xml = '<?xml version="1.0" ?><test><inside id="one" name="en-us"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('inside[name|="us"]'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + } + + public function testPseudoClassLang() { + + $xml = '<?xml version="1.0" ?><test><inside lang="en-us" id="one"/>Text<inside/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':lang(en-us)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('inside:lang(en)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('inside:lang(us)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + $xml = '<?xml version="1.0" ?><test><inside lang="en-us" id="one"/>Text<inside lang="us" id="two"/></test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':lang(us)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->nthMatch($matches, 1); + $this->assertEquals('two', $match->getAttribute('id')); + + $xml = '<?xml version="1.0" ?> + <test xmlns="http://aleph-null.tv/xml" xmlns:xml="http://www.w3.org/XML/1998/namespace"> + <inside lang="en-us" id="one"/>Text + <inside xml:lang="en-us" id="two"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':lang(us)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $match = $this->nthMatch($matches, 1); + $this->assertEquals('two', $match->getAttribute('id')); + } + + public function testPseudoClassEnabledDisabledChecked() { + $xml = '<?xml version="1.0" ?> + <test> + <inside enabled="enabled" id="one"/>Text + <inside disabled="disabled" id="two"/> + <inside checked="FOOOOO" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':enabled'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('one', $match->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':disabled'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('two', $match->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':checked()'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $match = $this->firstMatch($matches); + $this->assertEquals('three', $match->getAttribute('id')); + } + + public function testPseudoClassLink() { + $xml = '<?xml version="1.0"?><a><b href="foo"/><c href="foo"/></a>'; + $qp = qp($xml, ':link'); + $this->assertEquals(2, $qp->size()); + } + + public function testPseudoClassXReset() { + $xml = '<?xml version="1.0" ?> + <test> + <inside enabled="enabled" id="one"/>Text + <inside disabled="disabled" id="two"/> + <inside checked="FOOOOO" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('inside'); + $matches = $handler->getMatches(); + $this->assertEquals(3, $matches->count()); + $handler->find(':x-reset'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('test', $this->firstMatch($matches)->tagName); + } + + public function testPseudoClassRoot() { + $xml = '<?xml version="1.0" ?> + <test> + <inside enabled="enabled" id="one"/>Text + <inside disabled="disabled" id="two"/> + <inside checked="FOOOOO" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + $start = $doc->getElementsByTagName('inside'); + + // Start "deep in the doc" and traverse backward. + $handler = new QueryPathEventHandler($start); + $handler->find(':root'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('test', $this->firstMatch($matches)->tagName); + } + + // Test removed so I can re-declare + // listPeerElements as private. + public function xtestListPeerElements() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + <i class="even" id="four"/> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test full list + $handler = new QueryPathEventHandler($doc); + $handler->find('#one'); + $matches = $handler->getMatches(); + $peers = $handler->listPeerElements($this->firstMatch($matches)); + $this->assertEquals(6, count($peers)); + } + /* + public function testChildAtIndex() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + <i class="even" id="four"/> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test full list + $handler = new QueryPathEventHandler($doc); + $handler->find('test:child-at-index(1)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->nthMatch($matches, 1)->getAttribute('id')); + }*/ + + public function testPseudoClassNthChild() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + <i class="even" id="four"/> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // 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)'); + $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()); + + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"> + <i class="odd" id="inner-one"/> + <i class="even" id="inner-two"/> + </i> + <i class="even" id="four"/> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test nested items. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:nth-child(odd)'); + $matches = $handler->getMatches(); + $this->assertEquals(4, $matches->count()); + $matchIDs = array(); + foreach ($matches as $m) { + $matchIDs[] = $m->getAttribute('id'); + } +// $matchIDs = sort($matchIDs); + $this->assertEquals(array('one', 'three', 'inner-one', 'five'), $matchIDs); + //$this->assertEquals('inner-one', $matches[3]->getAttribute('id')); + + } + public function testPseudoClassOnlyChild() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test single last child. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:only-child'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="odd" id="two"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test single last child. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:only-child'); + $matches = $handler->getMatches(); + $this->assertEquals(0, $matches->count()); + //$this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassOnlyOfType() { + // TODO: Added this late (it was missing in original test), + // and I'm not sure if the assumed behavior is correct. + + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test single last child. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:only-of-type'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="odd" id="two"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test single last child. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:only-of-type'); + $matches = $handler->getMatches(); + $this->assertEquals(0, $matches->count()); + } + + public function testPseudoClassFirstChild() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + <i class="even" id="four"> + <i class="odd" id="inner-one"/> + <i class="even" id="inner-two"/> + <i class="odd" id="inner-three"/> + <i class="even" id="inner-four"/> + </i> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test single last child. + $handler = new QueryPathEventHandler($doc); + $handler->find('#four > i:first-child'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('inner-one', $this->firstMatch($matches)->getAttribute('id')); + + // Test for two last children + $handler = new QueryPathEventHandler($doc); + $handler->find('i:first-child'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('inner-one', $this->nthMatch($matches, 1)->getAttribute('id')); + } + + public function testPseudoClassLastChild() { + //print '----' . PHP_EOL; + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + <i class="even" id="four"> + <i class="odd" id="inner-one"/> + <i class="even" id="inner-two"/> + <i class="odd" id="inner-three"/> + <i class="even" id="inner-four"/> + </i> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test single last child. + $handler = new QueryPathEventHandler($doc); + $handler->find('#four > i:last-child'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('inner-four', $this->nthMatch($matches, 0)->getAttribute('id')); + + // Test for two last children + $handler = new QueryPathEventHandler($doc); + $handler->find('i:last-child'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('inner-four', $this->nthMatch($matches, 0)->getAttribute('id')); + $this->assertEquals('five', $this->nthMatch($matches, 1)->getAttribute('id')); + } + + public function testPseudoClassNthLastChild() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + <i class="even" id="four"> + <i class="odd" id="inner-one"/> + <i class="even" id="inner-two"/> + <i class="odd" id="inner-three"/> + <i class="even" id="inner-four"/> + </i> + <i class="odd" id="five"/> + <e class="even" id="six"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('#four > i:nth-last-child(odd)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('inner-two', $this->nthMatch($matches, 0)->getAttribute('id')); + $this->assertEquals('inner-four', $this->nthMatch($matches, 1)->getAttribute('id')); + + // According to spec, this should be last two elements. + $handler = new QueryPathEventHandler($doc); + $handler->find('#four > i:nth-last-child(-1n+2)'); + $matches = $handler->getMatches(); + //print $this->firstMatch($matches)->getAttribute('id'); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('inner-three', $this->nthMatch($matches, 0)->getAttribute('id')); + $this->assertEquals('inner-four', $this->nthMatch($matches, 1)->getAttribute('id')); + } + + public function testPseudoClassFirstOfType() { + $xml = '<?xml version="1.0" ?> + <test> + <n class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:first-of-type(odd)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassNthFirstOfType() { + $xml = '<?xml version="1.0" ?> + <test> + <n class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:first-of-type(1)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassLastOfType() { + $xml = '<?xml version="1.0" ?> + <test> + <n class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:last-of-type(odd)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('three', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoNthClassLastOfType() { + $xml = '<?xml version="1.0" ?> + <test> + <n class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:nth-last-of-type(1)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('three', $this->firstMatch($matches)->getAttribute('id')); + + + // Issue #56: an+b not working. + $xml = '<?xml version="1.0"?> + <root> + <div>I am the first div.</div> + <div>I am the second div.</div> + <div>I am the third div.</div> + <div>I am the fourth div.</div> + <div id="five">I am the fifth div.</div> + <div id="six">I am the sixth div.</div> + <div id="seven">I am the seventh div.</div> + </root>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('div:nth-last-of-type(-n+3)'); + $matches = $handler->getMatches(); + + $this->assertEquals(3, $matches->count()); + $this->assertEquals('five', $this->firstMatch($matches)->getAttribute('id')); + + } + + public function testPseudoClassEmpty() { + $xml = '<?xml version="1.0" ?> + <test> + <n class="odd" id="one"/> + <i class="even" id="two"></i> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('n:empty'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('i:empty'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassFirst() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:first'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassLast() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:last'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('three', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassGT() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:gt(1)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + } + public function testPseudoClassLT() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + // Test alternate rows from the end. + $handler = new QueryPathEventHandler($doc); + $handler->find('i:lt(3)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('one', $this->nthMatch($matches,0)->getAttribute('id')); + $this->assertEquals('two', $this->nthMatch($matches,1)->getAttribute('id')); + } + public function testPseudoClassNTH() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('i:nth(2)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('i:eq(2)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + } + public function testPseudoClassNthOfType() { + $xml = '<?xml version="1.0" ?> + <test> + <i class="odd" id="one"/> + <i class="even" id="two"/> + <i class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('i:nth-of-type(2)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassFormElements() { + $form = array('text', 'radio', 'checkbox', 'button', 'password'); + $xml = '<?xml version="1.0" ?> + <test> + <input type="%s" class="odd" id="one"/> + </test>'; + + foreach ($form as $item) { + $doc = new \DomDocument(); + $doc->loadXML(sprintf($xml, $item)); + + $handler = new QueryPathEventHandler($doc); + $handler->find(':' . $item); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + } + } + + public function testPseudoClassHeader() { + $xml = '<?xml version="1.0" ?> + <test> + <h1 class="odd" id="one"/> + <h2 class="even" id="two"/> + <h6 class="odd" id="three"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('test :header'); + $matches = $handler->getMatches(); + $this->assertEquals(3, $matches->count()); + $this->assertEquals('three', $this->nthMatch($matches, 2)->getAttribute('id')); + } + + public function testPseudoClassContains() { + $xml = '<?xml version="1.0" ?> + <test> + <p id="one">This is text.</p> + <p id="two"><i>More text</i></p> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains(This is text.)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('* :contains(More text)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count(), 'Matches two instance of same text?'); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains("This is text.")'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count(), 'Quoted text matches unquoted pcdata'); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains(\\\'This is text.\\\')'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count(), 'One match for quoted string.'); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + // Test for issue #32 + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains(text)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count(), 'Two matches for fragment of string.'); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + } + + public function testPseudoClassContainsExactly() { + $xml = '<?xml version="1.0" ?> + <test> + <p id="one">This is text.</p> + <p id="two"><i>More text</i></p> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains(This is text.)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('* :contains(More text)'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count(), 'Matches two instance of same text.'); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains("This is text.")'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count(), 'Quoted text matches unquoted pcdata'); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('p:contains(\\\'This is text.\\\')'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count(), 'One match for quoted string.'); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassHas() { + $xml = '<?xml version="1.0" ?> + <test> + <outer id="one"> + <inner/> + </outer> + <outer id="two"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('outer:has(inner)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoClassNot() { + $xml = '<?xml version="1.0" ?> + <test> + <outer id="one"> + <inner/> + </outer> + <outer id="two" class="notMe"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('outer:not(#one)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('outer:not(inner)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('outer:not(.notMe)'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testPseudoElement() { + $xml = '<?xml version="1.0" ?> + <test> + <outer id="one">Texts + + More text</outer> + <outer id="two" class="notMe"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('outer::first-letter'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('T', $this->firstMatch($matches)->textContent); + + $handler = new QueryPathEventHandler($doc); + $handler->find('outer::first-line'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('Texts', $this->firstMatch($matches)->textContent); + } + + public function testAdjacent() { + $xml = '<?xml version="1.0" ?> + <test> + <li id="one"/><li id="two"/><li id="three"> + <li id="inner-one"> + <li id="inner-inner-one"/> + <li id="inner-inner-one"/> + </li> + <li id="inner-two"/> + </li> + <li id="four"/> + <li id="five"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('#one + li'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('two', $this->firstMatch($matches)->getAttribute('id')); + + // Tell it to ignore whitespace nodes. + $doc->loadXML($xml, LIBXML_NOBLANKS); + + // Test with whitespace sensitivity weakened. + $handler = new QueryPathEventHandler($doc); + $handler->find('#four + li'); + $matches = $handler->getMatches(); + $this->assertEquals(1, $matches->count()); + $this->assertEquals('five', $this->firstMatch($matches)->getAttribute('id')); + } + public function testAnotherSelector() { + $xml = '<?xml version="1.0" ?> + <test> + <li id="one"/><li id="two"/><li id="three"> + <li id="inner-one"> + <li id="inner-inner-one"/> + <li id="inner-inner-one"/> + </li> + <li id="inner-two"/> + </li> + <li id="four"/> + <li id="five"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('#one, #two'); + $matches = $handler->getMatches(); + //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL; + $this->assertEquals(2, $matches->count()); + $this->assertEquals('two', $this->nthMatch($matches, 1)->getAttribute('id')); + + } + public function testSibling() { + $xml = '<?xml version="1.0" ?> + <test> + <li id="one"/><li id="two"/><li id="three"> + <li id="inner-one"> + <li id="inner-inner-one"/> + <il id="inner-inner-two"/> + <li id="dont-match-me"/> + </li> + <li id="inner-two"/> + </li> + <li id="four"/> + <li id="five"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('#one ~ li'); + $matches = $handler->getMatches(); + //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL; + $this->assertEquals(4, $matches->count()); + $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('#two ~ li'); + $matches = $handler->getMatches(); + //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL; + $this->assertEquals(3, $matches->count()); + //$this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('#inner-one > li ~ il'); + $matches = $handler->getMatches(); + //print $this->firstMatch($matches)->getAttribute('id') . PHP_EOL; + $this->assertEquals(1, $matches->count()); + $this->assertEquals('inner-inner-two', $this->firstMatch($matches)->getAttribute('id')); + } + + public function testAnyDescendant() { + $xml = '<?xml version="1.0" ?> + <test> + <li id="one"/><li id="two"/><li id="three"> + <li id="inner-one" class="foo"> + <li id="inner-inner-one" class="foo"/> + <il id="inner-inner-two"/> + <li id="dont-match-me"/> + </li> + <li id="inner-two"/> + </li> + <li id="four"/> + <li id="five"/> + </test>'; + $doc = new \DomDocument(); + $doc->loadXML($xml); + + $handler = new QueryPathEventHandler($doc); + $handler->find('*'); + $matches = $handler->getMatches(); + $this->assertEquals(11, $matches->count()); + + $handler = new QueryPathEventHandler($doc); + $handler->find('*.foo'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('inner-inner-one', $this->nthMatch($matches, 1)->getAttribute('id')); + + $handler = new QueryPathEventHandler($doc); + $handler->find('test > li *.foo'); + $matches = $handler->getMatches(); + $this->assertEquals(2, $matches->count()); + $this->assertEquals('inner-inner-one', $this->nthMatch($matches, 1)->getAttribute('id')); + } +} diff --git a/lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php b/lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php new file mode 100644 index 0000000..495f1f5 --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php @@ -0,0 +1,120 @@ +<?php +namespace QueryPath\Tests; +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\Selector, + \QueryPath\CSS\SimpleSelector, + \QueryPath\CSS\EventHandler; + +class SelectorTest extends TestCase { + + protected function parse($selector) { + $handler = new \QueryPath\CSS\Selector(); + $parser = new \QueryPath\CSS\Parser($selector, $handler); + $parser->parse(); + return $handler; + } + + public function testElement() { + $selector = $this->parse('test')->toArray(); + + $this->assertEquals(1, count($selector)); + $this->assertEquals('test', $selector[0]['0']->element); + } + + public function testElementNS() { + $selector = $this->parse('foo|test')->toArray(); + + $this->assertEquals(1, count($selector)); + $this->assertEquals('test', $selector[0]['0']->element); + $this->assertEquals('foo', $selector[0]['0']->ns); + } + + public function testId() { + $selector = $this->parse('#test')->toArray(); + + $this->assertEquals(1, count($selector)); + $this->assertEquals('test', $selector[0][0]->id); + } + + public function testClasses() { + $selector = $this->parse('.test')->toArray(); + + $this->assertEquals(1, count($selector)); + $this->assertEquals('test', $selector[0][0]->classes[0]); + + $selector = $this->parse('.test.foo.bar')->toArray(); + $this->assertEquals('test', $selector[0][0]->classes[0]); + $this->assertEquals('foo', $selector[0][0]->classes[1]); + $this->assertEquals('bar', $selector[0][0]->classes[2]); + + } + + public function testAttributes() { + $selector = $this->parse('foo[bar=baz]')->toArray(); + $this->assertEquals(1, count($selector)); + $attrs = $selector[0][0]->attributes; + + $this->assertEquals(1, count($attrs)); + + $attr = $attrs[0]; + $this->assertEquals('bar', $attr['name']); + $this->assertEquals(EventHandler::isExactly, $attr['op']); + $this->assertEquals('baz', $attr['value']); + + $selector = $this->parse('foo[bar=baz][size=one]')->toArray(); + $attrs = $selector[0][0]->attributes; + + $this->assertEquals('one', $attrs[1]['value']); + } + + public function testAttributesNS() { + $selector = $this->parse('[myns|foo=bar]')->toArray(); + + $attr = $selector[0][0]->attributes[0]; + + $this->assertEquals('myns', $attr['ns']); + $this->assertEquals('foo', $attr['name']); + } + + public function testPseudoClasses() { + $selector = $this->parse('foo:first')->toArray(); + $pseudo = $selector[0][0]->pseudoClasses; + + $this->assertEquals(1, count($pseudo)); + + $this->assertEquals('first', $pseudo[0]['name']); + } + + public function testPseudoElements() { + $selector = $this->parse('foo::bar')->toArray(); + $pseudo = $selector[0][0]->pseudoElements; + + $this->assertEquals(1, count($pseudo)); + + $this->assertEquals('bar', $pseudo[0]); + } + + public function testCombinators() { + // This implies *>foo + $selector = $this->parse('>foo')->toArray(); + + $this->assertEquals(SimpleSelector::directDescendant, $selector[0][1]->combinator); + + // This will be a selector with three simples: + // 'bar' + // 'foo ' + // '*>' + $selector = $this->parse('>foo bar')->toArray(); + $this->assertNull($selector[0][0]->combinator); + $this->assertEquals(SimpleSelector::anyDescendant, $selector[0][1]->combinator); + $this->assertEquals(SimpleSelector::directDescendant, $selector[0][2]->combinator); + } + + public function testIterator() { + $selector = $this->parse('foo::bar'); + + $iterator = $selector->getIterator(); + $this->assertInstanceOf('\Iterator', $iterator); + } +} diff --git a/lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php b/lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php new file mode 100644 index 0000000..64a2261 --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php @@ -0,0 +1,23 @@ +<?php +/** @file + * CSS Event handling tests + */ +namespace QueryPath\Tests; + +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\Token; + +/** + * @ingroup querypath_tests + * @group CSS + */ +class TokenTest extends TestCase { + public function testName() { + + $this->assertEquals('character', (Token::name(0))); + $this->assertEquals('a legal non-alphanumeric character', (Token::name(99))); + $this->assertEquals('end of file', (Token::name(FALSE))); + $this->assertEquals(0, strpos(Token::name(22),'illegal character')); + } +} diff --git a/lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php b/lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php new file mode 100644 index 0000000..bdb8533 --- /dev/null +++ b/lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php @@ -0,0 +1,51 @@ +<?php +/** @file + * CSS Event handling tests + */ +namespace QueryPath\Tests; + +require_once __DIR__ . '/../TestCase.php'; + +use \QueryPath\CSS\DOMTraverser\Util; + +/** + * @ingroup querypath_tests + * @group CSS + */ +class UtilTest extends TestCase { + public function testRemoveQuotes() { + $this->assertEquals('foo', Util::removeQuotes('"foo"')); + $this->assertEquals('foo', Util::removeQuotes("'foo'")); + $this->assertEquals('"foo\'', Util::removeQuotes("\"foo'")); + $this->assertEquals('f"o"o', Util::removeQuotes('f"o"o')); + + } + public function testParseAnB() { + // even + $this->assertEquals(array(2, 0), Util::parseAnB('even')); + // odd + $this->assertEquals(array(2, 1), Util::parseAnB('odd')); + // 5 + $this->assertEquals(array(0, 5), Util::parseAnB('5')); + // +5 + $this->assertEquals(array(0, 5), Util::parseAnB('+5')); + // n + $this->assertEquals(array(1, 0), Util::parseAnB('n')); + // 2n + $this->assertEquals(array(2, 0), Util::parseAnB('2n')); + // -234n + $this->assertEquals(array(-234, 0), Util::parseAnB('-234n')); + // -2n+1 + $this->assertEquals(array(-2, 1), Util::parseAnB('-2n+1')); + // -2n + 1 + $this->assertEquals(array(-2, 1), Util::parseAnB(' -2n + 1 ')); + // +2n-1 + $this->assertEquals(array(2, -1), Util::parseAnB('2n-1')); + $this->assertEquals(array(2, -1), Util::parseAnB('2n - 1')); + // -n + 3 + $this->assertEquals(array(-1, 3), Util::parseAnB('-n+3')); + + // Test invalid values + $this->assertEquals(array(0, 0), Util::parseAnB('obviously + invalid')); + } +} |