summaryrefslogtreecommitdiff
path: root/lib/querypath/test/Tests/QueryPath/CSS
diff options
context:
space:
mode:
Diffstat (limited to 'lib/querypath/test/Tests/QueryPath/CSS')
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php357
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php520
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php828
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php1439
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php120
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php23
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php51
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'));
+ }
+}