From 9a9c04512e5dcb77c7fe5d850e3f2a0250cc160e Mon Sep 17 00:00:00 2001
From: emkael
Date: Wed, 18 Jan 2017 20:07:16 +0100
Subject: * Motor Sport Magazine feed provider
---
.../test/Tests/QueryPath/CSS/DOMTraverserTest.php | 357 ++++
.../test/Tests/QueryPath/CSS/ParserTest.php | 520 ++++++
.../test/Tests/QueryPath/CSS/PseudoClassTest.php | 828 +++++++++
.../QueryPath/CSS/QueryPathEventHandlerTest.php | 1439 +++++++++++++++
.../test/Tests/QueryPath/CSS/SelectorTest.php | 120 ++
.../test/Tests/QueryPath/CSS/TokenTest.php | 23 +
.../test/Tests/QueryPath/CSS/UtilTest.php | 51 +
.../test/Tests/QueryPath/DOMQueryTest.php | 1865 ++++++++++++++++++++
.../test/Tests/QueryPath/EntitiesTest.php | 54 +
.../test/Tests/QueryPath/ExtensionTest.php | 153 ++
.../test/Tests/QueryPath/Extensions/QPXMLTest.php | 41 +
.../test/Tests/QueryPath/Extensions/QPXSLTest.php | 60 +
lib/querypath/test/Tests/QueryPath/OptionsTest.php | 60 +
.../test/Tests/QueryPath/QueryPathTest.php | 56 +
lib/querypath/test/Tests/QueryPath/TestCase.php | 22 +
lib/querypath/test/Tests/QueryPath/XMLIshTest.php | 63 +
16 files changed, 5712 insertions(+)
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/DOMTraverserTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/PseudoClassTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/QueryPathEventHandlerTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/SelectorTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/TokenTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/CSS/UtilTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/DOMQueryTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/EntitiesTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/ExtensionTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/OptionsTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/QueryPathTest.php
create mode 100644 lib/querypath/test/Tests/QueryPath/TestCase.php
create mode 100644 lib/querypath/test/Tests/QueryPath/XMLIshTest.php
(limited to 'lib/querypath/test/Tests')
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 @@
+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 @@
+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 @@
+loadXML($string);
+
+ $found = $doc->getElementsByTagName($tagname)->item(0);
+
+ return array($found, $doc->documentElement);
+
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testUnknownPseudoClass() {
+ $xml = 'test ';
+
+ list($ele, $root) = $this->doc($xml, 'foo');
+ $ps = new PseudoClass();
+
+ $ps->elementMatches('TotallyFake', $ele, $root);
+ }
+
+ public function testLang() {
+ $xml = 'test ';
+
+ 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 = 'test ';
+
+ 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 = 'test ';
+
+ 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 = 'test ';
+
+ 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 = 'TEST ';
+
+ 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 = 'This is a test of :contains. ';
+
+ 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 = 'This is a test of :contains-exactly. ';
+
+ 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 = ' ';
+
+ 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 = ' ';
+
+ 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 = '
';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertFalse($ret);
+
+ $xml = '
> ';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertFalse($ret);
+
+ $xml = 'TEST
';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertTrue($ret);
+
+ $xml = '
';
+ list($ele, $root) = $this->doc($xml, 'p');
+ $ret = $ps->elementMatches('parent', $ele, $root);
+ $this->assertTrue($ret);
+
+ }
+ public function testFirst() {
+ $ps = new PseudoClass();
+ $xml = '
';
+
+ 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 = '
';
+
+ 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 = ' ';
+
+ 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 = ' ';
+ 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 = 'test ';
+
+ 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 = 'test ';
+ $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 = ' ';
+ $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 = ' ';
+ $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 = ' ';
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= ' ';
+ $xml .= ' ';
+
+ 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 .= ' ';
+ $xml .= ' ';
+
+ $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 = 'test ';
+
+ 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 = '
';
+
+ 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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 .= str_repeat(' ', 5);
+ $xml .= ' ';
+
+ $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 = 'test ';
+
+ 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 = 'test ';
+
+ 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 = 'test ';
+
+ 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 @@
+
+
+
+ This is the title
+
+
+
+ Nada
+
8
+
+ Odd
+ Even
+ Odd
+ Even
+ Odd
+ Even
+ Odd
+ Even
+ Odd
+ Even
+
+
+
+ ';
+
+ 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 = ' Text ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $doc = new \DomDocument();
+ $doc->loadXML($xml);
+
+ $handler = new QueryPathEventHandler($doc);
+ $handler->find('myns\:mytest');
+ }
+
+ public function testElement() {
+ $xml = ' Text ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $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 = '
+
+
+ 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 = ' Text ';
+ $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 = ' Text ';
+ $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 = '
+
+
+ Text
+
+
+
+ ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $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 = ' Text ';
+ $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 = '
+
+ Text
+
+ ';
+ $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 = '
+
+ Text
+
+
+ ';
+ $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 = ' ';
+ $qp = qp($xml, ':link');
+ $this->assertEquals(2, $qp->size());
+ }
+
+ public function testPseudoClassXReset() {
+ $xml = '
+
+ Text
+
+
+ ';
+ $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 = '
+
+ Text
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+ ';
+ $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 = '
+
+
+
+ ';
+ $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 = '
+
+
+ ';
+ $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 = '
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+ I am the first div.
+ I am the second div.
+ I am the third div.
+ I am the fourth div.
+ I am the fifth div.
+ I am the sixth div.
+ I am the seventh div.
+ ';
+ $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 = '
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+ ';
+ $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 = '
+
+
+ ';
+
+ 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 = '
+
+
+
+
+ ';
+ $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 = '
+
+ This is text.
+ More text
+ ';
+ $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 = '
+
+ This is text.
+ More text
+ ';
+ $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 = '
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+ ';
+ $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 = '
+
+ Texts
+
+ More text
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 = '
+
+
+
+
+
+
+
+
+
+
+
+ ';
+ $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 @@
+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 @@
+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 @@
+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'));
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/DOMQueryTest.php b/lib/querypath/test/Tests/QueryPath/DOMQueryTest.php
new file mode 100644
index 0000000..238555e
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/DOMQueryTest.php
@@ -0,0 +1,1865 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+
+namespace QueryPath\Tests;
+
+/** @addtogroup querypath_tests Tests
+ * Unit tests and regression tests for DOMQuery.
+ */
+
+use QueryPath\DOMQuery;
+use \Masterminds\HTML5;
+
+/** */
+//require_once 'PHPUnit/Autoload.php';
+require __DIR__ . '/../../../vendor/autoload.php';
+require_once __DIR__ . '/TestCase.php';
+
+define('DATA_FILE', __DIR__ . '/../../data.xml');
+define('DATA_HTML_FILE', __DIR__ . '/../../data.html');
+define('NO_WRITE_FILE', __DIR__ . '/../../no-write.xml');
+define('MEDIUM_FILE', __DIR__ . '/../../amplify.xml');
+define('HTML_IN_XML_FILE', __DIR__ . '/../../html.xml');
+
+/**
+ * Tests for DOM Query. Primarily, this is focused on the DomQueryImpl
+ * class which is exposed through the DomQuery interface and the dq()
+ * factory function.
+ * @ingroup querypath_tests
+ */
+class DOMQueryTest extends TestCase {
+
+ /**
+ * @group basic
+ */
+ public function testDOMQueryConstructors() {
+
+ // From XML file
+ $file = DATA_FILE;
+ $qp = qp($file);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // From XML file with context
+ $cxt = stream_context_create();
+ $qp = qp($file, NULL, array('context' => $cxt));
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // From XML string
+ $str = ' ';
+ $qp = qp($str);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // From SimpleXML
+ $str = ' ';
+ $qp = qp(simplexml_load_string($str));
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // Test from DOMDocument
+ $qp = qp(\DOMDocument::loadXML($str));
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // Now with a selector:
+ $qp = qp($file, '#head');
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertEquals($qp->get(0)->tagName, 'head');
+
+ // Test HTML:
+ $htmlFile = DATA_HTML_FILE;
+ $qp = qp($htmlFile);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // Test with another DOMQuery.
+ $qp = qp($qp);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // Test from array of DOMNodes
+ $array = $qp->get();
+ $qp = qp($array);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ }
+ /**
+ * Test alternate constructors.
+ * @group basic
+ */
+ public function testDOMQueryHtmlConstructors() {
+ $qp = htmlqp(\QueryPath::HTML_STUB);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // Bad BR tag.
+ $borken = ' ';
+ $qp = htmlqp($borken);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // XHTML Faker
+ $borken = ' ';
+ $qp = htmlqp($borken);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // HTML in a file that looks like XML.
+ $qp = htmlqp(HTML_IN_XML_FILE);
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertTrue($qp->get(0) instanceof \DOMNode);
+
+ // HTML5
+ $html5 = new \Masterminds\HTML5();
+ $dom = $html5->loadHTML(\QueryPath::HTML_STUB);
+ qp($dom,'html');
+
+ // Stripping #13 (CR) from HTML.
+ $borken = '' . chr(13) . '
';
+ $this->assertFalse(strpos(htmlqp($borken)->html(), '
'), 'Test that CRs are not encoded.');
+
+ // Regression for #58: Make sure we aren't getting
encoded.
+ $borken = '';
+
+ $this->assertFalse(strpos(htmlqp($borken)->html(), '
'), 'Test that LF is not encoded.');
+
+ // Low ASCII in a file
+ $borken = '' . chr(27) . '
';
+ $this->assertEquals(1, htmlqp($borken, '#after')->size());
+ }
+
+ public function testForTests() {
+ $qp_methods = get_class_methods('\QueryPath\DOMQuery');
+ $test_methods = get_class_methods('\QueryPath\Tests\DOMQueryTest');
+
+ $ignore = array("__construct", "__call", "__clone", "get", "getOptions", "setMatches", "toArray", "getIterator");
+
+ $test_methods = array_map('strtolower', $test_methods);
+
+ foreach($qp_methods as $q) {
+ if(in_array($q, $ignore)) continue;
+ $this->assertTrue(in_array(strtolower("test".$q), $test_methods), $q . ' does not have a test method.');
+ }
+ }
+
+ public function testOptionXMLEncoding() {
+ $xml = qp(NULL, NULL, array('encoding' => 'iso-8859-1'))->append(' ')->xml();
+ $iso_found = preg_match('/iso-8859-1/', $xml) == 1;
+
+ $this->assertTrue($iso_found, 'Encoding should be iso-8859-1 in ' . $xml . 'Found ' . $iso_found);
+
+ $iso_found = preg_match('/utf-8/', $xml) == 1;
+ $this->assertFalse($iso_found, 'Encoding should not be utf-8 in ' . $xml);
+
+ $xml = qp(' ', NULL, array('encoding' => 'iso-8859-1'))->xml();
+ $iso_found = preg_match('/utf-8/', $xml) == 1;
+ $this->assertTrue($iso_found, 'Encoding should be utf-8 in ' . $xml);
+
+ $iso_found = preg_match('/iso-8859-1/', $xml) == 1;
+ $this->assertFalse($iso_found, 'Encoding should not be utf-8 in ' . $xml);
+
+ }
+
+ public function testQPAbstractFactory() {
+ $options = array('QueryPath_class' => '\QueryPath\Tests\QueryPathExtended');
+ $qp = qp(NULL, NULL, $options);
+ $this->assertTrue($qp instanceof QueryPathExtended, 'Is instance of extending class.');
+ $this->assertTrue($qp->foonator(), 'Has special foonator() function.');
+ }
+
+ public function testQPAbstractFactoryIterating() {
+ $xml = ' ';
+ $options = array('QueryPath_class' => '\QueryPath\Tests\QueryPathExtended');
+ foreach(qp($xml, 'i', $options) as $item) {
+ $this->assertTrue($item instanceof QueryPathExtended, 'Is instance of extending class.');
+ }
+
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testFailedCall() {
+ // This should hit __call() and then fail.
+ qp()->fooMethod();
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testFailedObjectConstruction() {
+ qp(new \stdClass());
+ }
+
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testFailedHTTPLoad() {
+ try {
+ qp('http://localhost:8877/no_such_file.xml');
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testFailedHTTPLoadWithContext() {
+ try {
+ qp('http://localhost:8877/no_such_file.xml', NULL, array('foo' => 'bar'));
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testFailedParseHTMLElement() {
+ try {
+ qp('&foonator; ', NULL);
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testFailedParseXMLElement() {
+ try {
+ qp('foonator; ', NULL);
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+ public function testIgnoreParserWarnings() {
+ $qp = @qp('BAD! ', NULL, array('ignore_parser_warnings' => TRUE));
+ $this->assertTrue(strpos($qp->html(), 'BAD! ') !== FALSE);
+
+ \QueryPath\Options::merge(array('ignore_parser_warnings' => TRUE));
+ $qp = @qp('BAD! ');
+ $this->assertTrue(strpos($qp->html(), 'BAD! ') !== FALSE);
+
+ $qp = @qp('BAD! ');
+ $this->assertTrue(strpos($qp->html(), 'BAD! ') !== FALSE, $qp->html());
+ \QueryPath\Options::set(array()); // Reset to empty options.
+ }
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testFailedParseNonMarkup() {
+ try {
+ qp('<23dfadf', NULL);
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testFailedParseEntity() {
+ try {
+ qp('&foonator; ', NULL);
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ public function testReplaceEntitiesOption() {
+ $path = ' ';
+ $xml = qp($path, NULL, array('replace_entities' => TRUE))->xml('& ')->xml();
+ $this->assertTrue(strpos($xml, '& ') !== FALSE);
+
+ $xml = qp($path, NULL, array('replace_entities' => TRUE))->html('& ')->xml();
+ $this->assertTrue(strpos($xml, '& ') !== FALSE);
+
+ $xml = qp($path, NULL, array('replace_entities' => TRUE))->xhtml('& ')->xml();
+ $this->assertTrue(strpos($xml, '& ') !== FALSE);
+
+ \QueryPath\Options::set(array('replace_entities' => TRUE));
+ $this->assertTrue(strpos($xml, '& ') !== FALSE);
+ \QueryPath\Options::set(array());
+ }
+
+ /**
+ * @group basic
+ */
+ public function testFind() {
+ $file = DATA_FILE;
+ $qp = qp($file)->find('#head');
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertEquals($qp->get(0)->tagName, 'head');
+
+ $this->assertEquals('inner', qp($file)->find('.innerClass')->tag());
+
+ $string = ' Test ';
+ $qp = qp($string)->find('root');
+ $this->assertEquals(1, count($qp->get()), 'Check tag.');
+ $this->assertEquals($qp->get(0)->tagName, 'root');
+
+ $string = 'Test ';
+ $qp = qp($string)->find('.findme');
+ $this->assertEquals(1, count($qp->get()), 'Check class.');
+ $this->assertEquals($qp->get(0)->tagName, 'root');
+ }
+ public function testFindInPlace() {
+ $file = DATA_FILE;
+ $qp = qp($file)->find('#head');
+ $this->assertEquals(1, count($qp->get()));
+ $this->assertEquals($qp->get(0)->tagName, 'head');
+
+ $this->assertEquals('inner', qp($file)->find('.innerClass')->tag());
+
+ $string = ' Test ';
+ $qp = qp($string)->find('root');
+ $this->assertEquals(1, count($qp->get()), 'Check tag.');
+ $this->assertEquals($qp->get(0)->tagName, 'root');
+
+ $string = 'Test ';
+ $qp = qp($string)->find('.findme');
+ $this->assertEquals(1, count($qp->get()), 'Check class.');
+ $this->assertEquals($qp->get(0)->tagName, 'root');
+ }
+
+ /**
+ * @group basic
+ */
+ public function testTop() {
+ $file = DATA_FILE;
+ $qp = qp($file)->find('li');
+ $this->assertGreaterThan(2, $qp->size());
+ $this->assertEquals(1, $qp->top()->size());
+
+ // Added for QP 2.0
+ $xml = ' ';
+ $qp = qp($xml, 'l');
+ $this->assertEquals(3, $qp->size());
+ $this->assertEquals(2, $qp->top('u')->size());
+ }
+
+ /**
+ * @group basic
+ */
+ public function testAttr() {
+ $file = DATA_FILE;
+
+ $qp = qp($file)->find('#head');
+ $this->assertEquals(1, $qp->size());
+ $this->assertEquals($qp->get(0)->getAttribute('id'), $qp->attr('id'));
+
+ $qp->attr('foo', 'bar');
+ $this->assertEquals('bar', $qp->attr('foo'));
+
+ $qp->attr(array('foo2' => 'bar', 'foo3' => 'baz'));
+ $this->assertEquals('baz', $qp->attr('foo3'));
+
+ // Check magic nodeType attribute:
+ $this->assertEquals(XML_ELEMENT_NODE, qp($file)->find('#head')->attr('nodeType'));
+
+ // Since QP 2.1
+ $xml = ' ';
+ $qp = qp($xml, 'one');
+ $attrs = $qp->attr();
+ $this->assertEquals(3, count($attrs), 'Three attributes');
+ $this->assertEquals('1', $attrs['a1'], 'Attribute a1 has value 1.');
+ }
+
+ /**
+ * @group basic
+ */
+ public function testHasAttr() {
+ $xml = '
';
+
+ $this->assertFalse(qp($xml, 'root')->hasAttr('foo'));
+ $this->assertTrue(qp($xml, 'div')->hasAttr('foo'));
+
+ $xml = '
';
+ $this->assertTrue(qp($xml, 'div')->hasAttr('foo'));
+
+ $xml = '
';
+ $this->assertFalse(qp($xml, 'div')->hasAttr('foo'));
+
+ $xml = '
';
+ $this->assertFalse(qp($xml, 'div')->hasAttr('foo'));
+ }
+
+ public function testVal() {
+ $qp = qp(' ', 'bar');
+ $this->assertEquals('test', $qp->val());
+
+ $qp = qp(' ', 'bar')->val('test');
+ $this->assertEquals('test', $qp->attr('value'));
+ }
+
+ public function testCss() {
+ $file = DATA_FILE;
+ $this->assertEquals('foo: bar;', qp($file, 'unary')->css('foo', 'bar')->attr('style'));
+ $this->assertEquals('foo: bar;', qp($file, 'unary')->css('foo', 'bar')->css());
+ $this->assertEquals('foo: bar;', qp($file, 'unary')->css(array('foo' =>'bar'))->css());
+
+ // Issue #28: Setting styles in sequence should not result in the second
+ // style overwriting the first style:
+ $qp = qp($file, 'unary')->css('color', 'blue')->css('background-color', 'white');
+
+ $expects = 'color: blue;background-color: white;';
+ $actual = $qp->css();
+ $this->assertEquals(bin2hex($expects), bin2hex($actual), 'Two css calls should result in two attrs.');
+
+ // Make sure array merges work.
+ $qp = qp($file, 'unary')->css('a','a')->css(array('b'=>'b', 'c'=>'c'));
+ $this->assertEquals('a: a;b: b;c: c;', $qp->css());
+
+ // Make sure that second assignment overrides first assignment.
+ $qp = qp($file, 'unary')->css('a','a')->css(array('b'=>'b', 'a'=>'c'));
+ $this->assertEquals('a: c;b: b;', $qp->css());
+ }
+
+ public function testRemoveAttr() {
+ $file = DATA_FILE;
+
+ $qp = qp($file, 'inner')->removeAttr('class');
+ $this->assertEquals(2, $qp->size());
+ $this->assertFalse($qp->get(0)->hasAttribute('class'));
+
+ }
+
+ public function testEq() {
+ $file = DATA_FILE;
+ $qp = qp($file)->find('li')->eq(0);
+ $this->assertEquals(1, $qp->size());
+ $this->assertEquals($qp->attr('id'), 'one');
+ $this->assertEquals(1, qp($file, 'inner')->eq(0)->size());
+ $this->assertEquals(1, qp($file, 'li')->eq(0)->size());
+ $this->assertEquals("Hello", qp($file, 'li')->eq(0)->text());
+ $this->assertEquals("Last", qp($file, 'li')->eq(3)->text());
+ }
+
+ public function testIs() {
+ $file = DATA_FILE;
+ $this->assertTrue(qp($file)->find('#one')->is('#one'));
+ $this->assertTrue(qp($file)->find('li')->is('#one'));
+
+ $qp = qp($file)->find('#one');
+ $ele = $qp->get(0);
+ $this->assertTrue($qp->top('#one')->is($ele));
+
+ $qp = qp($file)->find('#one');
+ $ele = $qp->get(0);
+ $ele2 = $qp->top('#two')->get(0);
+
+ $list = new \SplDoublyLinkedList();
+ $list->push($ele);
+ $list->push($ele2);
+ $this->assertEquals(2, count($list));
+ //$this->assertEquals(2, )
+ $this->assertTrue($qp->top('#one,#two')->is($list));
+
+ }
+
+ public function testIndex() {
+ $xml = ' ';
+ $qp = qp($xml, 'bar');
+ $e1 = $qp->get(0);
+ $this->assertEquals(0, $qp->find('bar')->index($e1));
+ $this->assertFalse($qp->top()->find('#two')->index($e1));
+ }
+
+ public function testFilter() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file)->filter('li')->size());
+ $this->assertEquals(2, qp($file, 'inner')->filter('li')->size());
+ $this->assertEquals('inner-two', qp($file, 'inner')->filter('li')->eq(1)->attr('id'));
+ }
+
+ public function testFilterPreg() {
+ $xml = 'Foo
Moo
';
+ $qp = qp($xml, 'div')->filterPreg('/Foo/');
+
+ $this->assertEquals(1, $qp->Size());
+
+ // Check to make sure textContent is collected correctly.
+ $xml = 'Hello World
';
+ $qp = qp($xml, 'div')->filterPreg('/Hello\sWorld/');
+
+ $this->assertEquals(1, $qp->Size());
+ }
+
+ public function testFilterLambda() {
+ $file = DATA_FILE;
+ // Get all evens:
+ $l = 'return (($index + 1) % 2 == 0);';
+ $this->assertEquals(2, qp($file, 'li')->filterLambda($l)->size());
+ }
+
+ public function filterCallbackFunction($index, $item) {
+ return (($index + 1) % 2 == 0);
+ }
+
+
+ public function testFilterCallback() {
+ $file = DATA_FILE;
+ $cb = array($this, 'filterCallbackFunction');
+ $this->assertEquals(2, qp($file, 'li')->filterCallback($cb)->size());
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testFailedFilterCallback() {
+ $file = DATA_FILE;
+ $cb = array($this, 'noSuchFunction');
+ qp($file, 'li')->filterCallback($cb)->size();
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testFailedMapCallback() {
+ $file = DATA_FILE;
+ $cb = array($this, 'noSuchFunction');
+ qp($file, 'li')->map($cb)->size();
+ }
+
+
+ public function testNot() {
+ $file = DATA_FILE;
+
+ // Test with selector
+ $qp = qp($file, 'li:odd')->not('#one');
+ $this->assertEquals(2, $qp->size());
+
+ // Test with DOM Element
+ $qp = qp($file, 'li');
+ $el = $qp->branch()->filter('#one')->get(0);
+ $this->assertTrue($el instanceof \DOMElement, "Is DOM element.");
+ $this->assertEquals(4, $qp->not($el)->size());
+
+ // Test with array of DOM Elements
+ $qp = qp($file, 'li');
+ $arr = $qp->get();
+ $this->assertEquals(count($arr), $qp->size());
+ array_shift($arr);
+ $this->assertEquals(1, $qp->not($arr)->size());
+ }
+
+ public function testSlice() {
+ $file = DATA_FILE;
+ // There are five elements
+ $qp = qp($file, 'li')->slice(1);
+ $this->assertEquals(4, $qp->size());
+
+ // The first item in the matches should be #two.
+ $this->assertEquals('two', $qp->attr('id'));
+
+ // THe last item should be #five
+ $this->assertEquals('five', $qp->eq(3)->attr('id'));
+
+ // This should not throw an error.
+ $this->assertEquals(4, qp($file, 'li')->slice(1, 9)->size());
+
+ $this->assertEquals(0, qp($file, 'li')->slice(9)->size());
+
+ // The first item should be #two, the last #three
+ $qp = qp($file, 'li')->slice(1, 2);
+ $this->assertEquals(2, $qp->size());
+ $this->assertEquals('two', $qp->attr('id'));
+ $this->assertEquals('three', $qp->eq(1)->attr('id'));
+ }
+
+ public function mapCallbackFunction($index, $item) {
+ if ($index == 1) {
+ return FALSE;
+ }
+ if ($index == 2) {
+ return array(1, 2, 3);
+ }
+ return $index;
+ }
+
+ public function testMap() {
+ $file = DATA_FILE;
+ $fn = 'mapCallbackFunction';
+ $this->assertEquals(7, qp($file, 'li')->map(array($this, $fn))->size());
+ }
+
+ public function eachCallbackFunction($index, $item) {
+ if ($index < 2) {
+ qp($item)->attr('class', 'test');
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+ public function testEach() {
+ $file = DATA_FILE;
+ $fn = 'eachCallbackFunction';
+ $res = qp($file, 'li')->each(array($this, $fn));
+ $this->assertEquals(5, $res->size());
+ $this->assertFalse($res->get(4)->getAttribute('class') === NULL);
+ $this->assertEquals('test', $res->eq(1)->attr('class'));
+
+ // Test when each runs out of things to test before returning.
+ $res = qp($file, '#one')->each(array($this, $fn));
+ $this->assertEquals(1, $res->size());
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testEachOnInvalidCallback() {
+ $file = DATA_FILE;
+ $fn = 'eachCallbackFunctionFake';
+ $res = qp($file, 'li')->each(array($this, $fn));
+ }
+
+ public function testEachLambda() {
+ $file = DATA_FILE;
+ $fn = 'qp($item)->attr("class", "foo");';
+ $res = qp($file, 'li')->eachLambda($fn);
+ $this->assertEquals('foo', $res->eq(1)->attr('class'));
+ }
+
+ public function testDeepest() {
+ $str = '
+
+
+
+
+
+
+
+ ';
+ $deepest = qp($str)->deepest();
+ $this->assertEquals(2, $deepest->size());
+ $this->assertEquals('four', $deepest->get(0)->tagName);
+ $this->assertEquals('banana', $deepest->get(1)->tagName);
+
+ $deepest = qp($str, 'one')->deepest();
+ $this->assertEquals(2, $deepest->size());
+ $this->assertEquals('four', $deepest->get(0)->tagName);
+ $this->assertEquals('banana', $deepest->get(1)->tagName);
+
+ $str = '
+
+ CDATA
+ ';
+ $this->assertEquals(1, qp($str)->deepest()->size());
+ }
+
+ public function testTag() {
+ $file = DATA_FILE;
+ $this->assertEquals('li', qp($file, 'li')->tag());
+ }
+
+ public function testAppend() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file,'unary')->append(' ')->find(':root > unary > test')->size());
+ $qp = qp($file,'#inner-one')->append(' ');
+
+ $appended = $qp->find('#appended');
+ $this->assertEquals(1, $appended->size());
+ $this->assertNull($appended->get(0)->nextSibling);
+
+ $this->assertEquals(2, qp($file, 'inner')->append(' ')->top()->find('test')->size());
+ $this->assertEquals(2, qp($file, 'inner')->append(qp(' '))->top()->find('test')->size());
+ $this->assertEquals(4, qp($file, 'inner')->append(qp(' ', 'test'))->top()->find('test')->size());
+
+ // Issue #6: This seems to break on Debian Etch systems... no idea why.
+ $this->assertEquals('test', qp()->append(' ')->top()->tag());
+
+ // Issue #7: Failure issues warnings
+ // This seems to be working as expected -- libxml emits
+ // parse errors.
+ //$this->assertEquals(NULL, qp()->append(' ')->append($simp);
+ $this->assertEquals(1, $qp->find('root')->size());
+
+ // Test with replace entities turned on:
+ $qp = qp($file, 'root', array('replace_entities' => TRUE))->append('»
');
+ // Note that we are using a UTF-8 » character, not an ASCII 187. This seems to cause
+ // problems on some Windows IDEs. So here we do it the ugly way.
+ $utf8raquo = '' . mb_convert_encoding(chr(187), 'utf-8', 'iso-8859-1') . '
';
+ //$this->assertEquals('»
', $qp->find('p')->html(), 'Entities are decoded to UTF-8 correctly.');
+ $this->assertEquals($utf8raquo, $qp->find('p')->html(), 'Entities are decoded to UTF-8 correctly.');
+
+ // Test with empty, mainly to make sure it doesn't explode.
+ $this->assertTrue(qp($file)->append('') instanceof DOMQuery);
+ }
+
+ /**
+ * @expectedException \QueryPath\ParseException
+ */
+ public function testAppendBadMarkup() {
+ $file = DATA_FILE;
+ try{
+ qp($file, 'root')->append(' ');
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testAppendBadObject() {
+ $file = DATA_FILE;
+ try{
+ qp($file, 'root')->append(new \stdClass);
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ public function testAppendTo() {
+ $file = DATA_FILE;
+ $dest = qp(' ', 'dest');
+ $qp = qp($file,'li')->appendTo($dest);
+ $this->assertEquals(5, $dest->find(':root li')->size());
+ }
+
+ public function testPrepend() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file,'unary')->prepend(' ')->find(':root > unary > test')->size());
+ $qp = qp($file,'#inner-one')->prepend(' ')->find('#appended');
+ $this->assertEquals(1, $qp->size());
+ $this->assertNull($qp->get(0)->previousSibling);
+
+ // Test repeated insert
+ $this->assertEquals(2, qp($file,'inner')->prepend(' ')->top()->find('test')->size());
+ $this->assertEquals(4, qp($file,'inner')->prepend(qp(' ', 'test'))->top()->find('test')->size());
+ }
+
+ public function testPrependTo() {
+ $file = DATA_FILE;
+ $dest = qp(' ', 'dest');
+ $qp = qp($file,'li')->prependTo($dest);
+ $this->assertEquals(5, $dest->find(':root li')->size());
+ }
+
+ public function testBefore() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file,'unary')->before(' ')->find(':root > test ~ unary')->size());
+ $this->assertEquals(1, qp($file,'unary')->before(' ')->top('head ~ test')->size());
+ $this->assertEquals('unary', qp($file,'unary')->before(' ')->top(':root > test')->get(0)->nextSibling->tagName);
+
+ // Test repeated insert
+ $this->assertEquals(2, qp($file,'inner')->before(' ')->top()->find('test')->size());
+ $this->assertEquals(4, qp($file,'inner')->before(qp(' ', 'test'))->top()->find('test')->size());
+ }
+
+ public function testAfter() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file,'unary')->after(' ')->top(':root > unary ~ test')->size());
+ $this->assertEquals('unary', qp($file,'unary')->after(' ')->top(':root > test')->get(0)->previousSibling->tagName);
+
+ $this->assertEquals(2, qp($file,'inner')->after(' ')->top()->find('test')->size());
+ $this->assertEquals(4, qp($file,'inner')->after(qp(' ', 'test'))->top()->find('test')->size());
+ }
+
+ public function testInsertBefore() {
+ $file = DATA_FILE;
+ $dest = qp(' ', 'dest');
+ $qp = qp($file,'li')->insertBefore($dest);
+ $this->assertEquals(5, $dest->top(':root > li')->size());
+ $this->assertEquals('li', $dest->end()->find('dest')->get(0)->previousSibling->tagName);
+ }
+ public function testInsertAfter() {
+ $file = DATA_FILE;
+ $dest = qp(' ', 'dest');
+ $qp = qp($file,'li')->insertAfter($dest);
+ //print $dest->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(5, $dest->top(':root > li')->size());
+ }
+ public function testReplaceWith() {
+ $file = DATA_FILE;
+ $qp = qp($file,'unary')->replaceWith(' ')->top('test');
+ //print $qp->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(1, $qp->size());
+
+ $qp = qp($file,'unary')->replaceWith(qp(' ', 'test'));
+ //print $qp->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(2, $qp->top()->find('test')->size());
+ }
+
+ public function testReplaceAll() {
+ $qp1 = qp(' ');
+ $doc = qp(' ')->get(0)->ownerDocument;
+
+ $qp2 = $qp1->find('l')->replaceAll('m', $doc);
+
+ $this->assertEquals(2, $qp2->top()->find('l')->size());
+ }
+
+ public function testUnwrap() {
+
+ // Unwrap center, and make sure junk goes away.
+ $xml = ' ';
+ $qp = qp($xml, 'center')->unwrap();
+ $this->assertEquals('root', $qp->top('center')->parent()->tag());
+ $this->assertEquals(0, $qp->top('junk')->size());
+
+ // Make sure it works on two nodes in the same parent.
+ $xml = ' ';
+ $qp = qp($xml, 'center')->unwrap();
+
+ // Make sure they were unwrapped
+ $this->assertEquals('root', $qp->top('center')->parent()->tag());
+
+ // Make sure both get copied.
+ $this->assertEquals(2, $qp->top('center')->size());
+
+ // Make sure they are in order.
+ $this->assertEquals('2', $qp->top('center:last')->attr('id'));
+
+ // Test on root element.
+ $xml = ' ';
+ $qp = qp($xml, 'center')->unwrap();
+ $this->assertEquals('center', $qp->top()->tag());
+
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testFailedUnwrap() {
+ // Cannot unwrap the root element.
+ $xml = ' ';
+ $qp = qp($xml, 'root')->unwrap();
+ $this->assertEquals('center', $qp->top()->tag());
+ }
+
+ public function testWrap() {
+ $file = DATA_FILE;
+ $xml = qp($file,'unary')->wrap('');
+ $this->assertTrue($xml instanceof DOMQuery);
+
+ $xml = qp($file,'unary')->wrap(' ')->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);
+
+ $xml = qp($file,'unary')->wrap(qp(' ', 'test'))->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);
+
+ $xml = qp($file,'li')->wrap(' ')->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(5, qp($xml, '.testWrap')->size());
+
+ $xml = qp($file,'li')->wrap(' ')->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(5, qp($xml, '.testWrap > inside > center > li')->size());
+ }
+
+ public function testWrapAll() {
+ $file = DATA_FILE;
+
+ $xml = qp($file,'unary')->wrapAll('');
+ $this->assertTrue($xml instanceof DOMQuery);
+
+ $xml = qp($file,'unary')->wrapAll(' ')->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);
+
+ $xml = qp($file,'unary')->wrapAll(qp(' ', 'test'))->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length);
+
+ $xml = qp($file,'li')->wrapAll(' ')->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(5, qp($xml, '.testWrap > inside > center > li')->size());
+
+ }
+
+ public function testWrapInner() {
+ $file = DATA_FILE;
+
+ $this->assertTrue(qp($file,'#inner-one')->wrapInner('') instanceof DOMQuery);
+
+ $xml = qp($file,'#inner-one')->wrapInner(' ')->get(0)->ownerDocument->saveXML();
+ // FIXME: 9 includes text nodes. Should fix this.
+ $this->assertEquals(9, qp($xml, '.testWrap')->get(0)->childNodes->length);
+
+ $xml = qp($file,'inner')->wrapInner(' ')->get(0)->ownerDocument->saveXML();
+ $this->assertEquals(9, qp($xml, '.testWrap')->get(0)->childNodes->length);
+ $this->assertEquals(3, qp($xml, '.testWrap')->get(1)->childNodes->length);
+
+ $qp = qp($file,'inner')->wrapInner(qp(' ', 'test'));
+ $this->assertEquals(2, $qp->find('inner > .testWrap')->size());
+ $this->assertEquals(0, $qp->find('.ignore')->size());
+ }
+
+ public function testRemove() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'li');
+ $start = $qp->size();
+ $finish = $qp->remove()->size();
+ $this->assertEquals($start, $finish);
+ $this->assertEquals(0, $qp->find(':root li')->size());
+
+ // Test for Issue #55
+ $data = 'test FAIL ';
+ $qp = qp($data);
+ $rem = $qp->remove('b');
+
+
+ $this->assertEquals(' FAIL', $rem->text());
+ $this->assertEquals('test', $qp->text());
+
+ // Test for Issue #63
+ $qp = qp($data);
+ $rem = $qp->remove('noSuchElement');
+ $this->assertEquals(0, count($rem));
+ }
+
+ public function testHasClass() {
+ $file = DATA_FILE;
+ $this->assertTrue(qp($file, '#inner-one')->hasClass('innerClass'));
+
+ $file = DATA_FILE;
+ $this->assertFalse(qp($file, '#inner-one')->hasClass('noSuchClass'));
+ }
+
+ public function testAddClass() {
+ $file = DATA_FILE;
+ $this->assertTrue(qp($file, '#inner-one')->addClass('testClass')->hasClass('testClass'));
+ }
+ public function testRemoveClass() {
+ $file = DATA_FILE;
+ // The add class tests to make sure that this works with multiple values.
+ $this->assertFalse(qp($file, '#inner-one')->removeClass('innerClass')->hasClass('innerClass'));
+ $this->assertTrue(qp($file, '#inner-one')->addClass('testClass')->removeClass('innerClass')->hasClass('testClass'));
+ }
+
+ public function testAdd() {
+ $file = DATA_FILE;
+ $this->assertEquals(7, qp($file, 'li')->add('inner')->size());
+ }
+
+ public function testEnd() {
+ $file = DATA_FILE;
+ $this->assertEquals(2, qp($file, 'inner')->find('li')->end()->size());
+ }
+
+ public function testAndSelf() {
+ $file = DATA_FILE;
+ $this->assertEquals(7, qp($file, 'inner')->find('li')->andSelf()->size());
+ }
+
+ public function testChildren() {
+ $file = DATA_FILE;
+ $this->assertEquals(5, qp($file, 'inner')->children()->size());
+ foreach (qp($file, 'inner')->children('li') as $kid) {
+ $this->assertEquals('li', $kid->tag());
+ }
+ $this->assertEquals(5, qp($file, 'inner')->children('li')->size());
+ $this->assertEquals(1, qp($file, ':root')->children('unary')->size());
+ }
+ public function testRemoveChildren() {
+ $file = DATA_FILE;
+ $this->assertEquals(0, qp($file, '#inner-one')->removeChildren()->find('li')->size());
+ }
+
+ public function testContents() {
+ $file = DATA_FILE;
+ $this->assertGreaterThan(5, qp($file, 'inner')->contents()->size());
+ // Two cdata nodes and one element node.
+ $this->assertEquals(3, qp($file, '#inner-two')->contents()->size());
+
+ // Issue #51: Under certain recursive conditions, this returns error.
+ // Warning: Whitespace is important in the markup beneath.
+ $xml = 'Hello
+
how are you
+
fine thank you
+
and you ?
+
+
+
+ ';
+ $cr = $this->contentsRecurse(qp($xml));
+ $this->assertEquals(14, count($cr), implode("\n", $cr));
+ }
+
+ public function testNS() {
+ $xml = 'test ';
+
+ $q = qp($xml, "e");
+
+ $this->assertEquals(1, $q->size());
+
+ $this->assertEquals("foo:bar", $q->ns());
+ }
+
+ /**
+ * Helper function for testContents().
+ * Based on problem reported in issue 51.
+ */
+ private function contentsRecurse($source, &$pack = array()) {
+ //static $i = 0;
+ //static $filter = "%d. Node type: %d, Content: '%s'\n";
+ $children = $source->contents();
+ //$node = $source->get(0);
+ $pack[] = 1; //sprintf($filter, ++$i, $node->nodeType, $source->html());
+
+ foreach ($children as $child) {
+ $pack += $this->contentsRecurse($child, $pack);
+ }
+
+ return $pack;
+ }
+
+ public function testSiblings() {
+ $file = DATA_FILE;
+ $this->assertEquals(3, qp($file, '#one')->siblings()->size());
+ $this->assertEquals(2, qp($file, 'unary')->siblings('inner')->size());
+ }
+
+ public function testXinclude() {
+
+ }
+
+ public function testHTML() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'unary');
+ $html = 'test ';
+ $this->assertEquals($html, $qp->html($html)->find('b')->html());
+
+ $html = 'foo bar';
+ // We expect a DocType to be prepended:
+ $this->assertEquals('html(), 0, 9));
+
+ // Check that HTML is not added to empty finds. Note the # is for a special
+ // case.
+ $this->assertEquals('', qp($html, '#nonexistant')->html('Hello
')->html());
+ $this->assertEquals('', qp($html, 'nonexistant')->html('Hello
')->html());
+
+ // We expect NULL if the document is empty.
+ $this->assertNull(qp()->html());
+
+ // Non-DOMNodes should not be rendered:
+ $fn = 'mapCallbackFunction';
+ $this->assertNull(qp($file, 'li')->map(array($this, $fn))->html());
+ }
+
+ public function testInnerHTML() {
+ $html = '';
+
+ $this->assertEquals('TestAgain
', qp($html,'#me')->innerHTML());
+ }
+
+ public function testInnerXML() {
+ $html = ' ';
+ $test = 'TestAgain1
';
+
+ $this->assertEquals($test, qp($html,'#me')->innerXML());
+
+ $html = ' ';
+ $test = 'TestAgain2
';
+
+ $this->assertEquals($test, qp($html,'#me')->innerXML());
+
+ $html = '
';
+ $test = '';
+ $this->assertEquals($test, qp($html,'#me')->innerXML());
+
+ $html = 'test ';
+ $test = 'test';
+ $this->assertEquals($test, qp($html,'#me')->innerXML());
+ }
+
+ public function testInnerXHTML() {
+ $html = '';
+
+ $this->assertEquals('TestAgain
', qp($html,'#me')->innerXHTML());
+
+ // Regression for issue #10: Tags should not be unary (e.g. we want , not )
+ $xml = 'foo ';
+ // Look for a closing tag
+ $regex = '/<\/br>/';
+ $this->assertRegExp($regex, qp($xml, '#me')->innerXHTML(), 'BR should have a closing tag.');
+ }
+
+ public function testXML() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'unary');
+ $xml = 'test ';
+ $this->assertEquals($xml, $qp->xml($xml)->find('b')->xml());
+
+ $xml = 'foo bar';
+ // We expect an XML declaration to be prepended:
+ $this->assertEquals('xml(), 0, 5));
+
+ // We don't want an XM/L declaration if xml(TRUE).
+ $xml = ' ';
+ $this->assertFalse(strpos(qp($xml)->xml(TRUE), 'assertNull(qp()->xml());
+
+ // Non-DOMNodes should not be rendered:
+ $fn = 'mapCallbackFunction';
+ $this->assertNull(qp($file, 'li')->map(array($this, $fn))->xml());
+ }
+
+ public function testXHTML() {
+ // throw new Exception();
+
+ $file = DATA_FILE;
+ $qp = qp($file, 'unary');
+ $xml = 'test ';
+ $this->assertEquals($xml, $qp->xml($xml)->find('b')->xhtml());
+
+ $xml = 'foo bar';
+ // We expect an XML declaration to be prepended:
+ $this->assertEquals('xhtml(), 0, 5));
+
+ // We don't want an XM/L declaration if xml(TRUE).
+ $xml = ' ';
+ $this->assertFalse(strpos(qp($xml)->xhtml(TRUE), 'assertNull(qp()->xhtml());
+
+ // Non-DOMNodes should not be rendered:
+ $fn = 'mapCallbackFunction';
+ $this->assertNull(qp($file, 'li')->map(array($this, $fn))->xhtml());
+
+ // Regression for issue #10: Tags should not be unary (e.g. we want , not )
+ $xml = 'foo
+
+ bar
+
+
+
+ ';
+
+ $xhtml = qp($xml)->xhtml();
+
+ //throw new Exception($xhtml);
+
+ // Look for a properly formatted BR unary tag:
+ $regex = '/ /';
+ $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.');
+
+ // Look for a properly formatted HR tag:
+ $regex = '/ /';
+ $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.');
+
+ // Ensure that script tag is not collapsed:
+ $regex = '/
+
+ foo bar';
+
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xml, 'tml')->writeXML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ // We expect an XML declaration at the top.
+ $this->assertEquals('writeXML($name);
+ $this->assertTrue(file_exists($name));
+ $this->assertTrue(qp($name) instanceof DOMQuery);
+ unlink($name);
+ }
+
+ public function testWriteXHTML() {
+ $xml = 'foo bar';
+
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xml, 'tml')->writeXHTML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ // We expect an XML declaration at the top.
+ $this->assertEquals('
+
+ foo bar';
+
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xml, 'html')->writeXHTML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ // We expect an XML declaration at the top.
+ $this->assertEquals('writeXHTML($name);
+ $this->assertTrue(file_exists($name));
+ $this->assertTrue(qp($name) instanceof DOMQuery);
+ unlink($name);
+
+ // Regression for issue #10 (keep closing tags in XHTML)
+ $xhtml = 'foo bar';
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xhtml, 'html')->writeXHTML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ $pattern = '/<\/script>/';
+ $this->assertRegExp($pattern, $out, 'Should be closing script tag.');
+
+ $pattern = '/<\/br>/';
+ $this->assertRegExp($pattern, $out, 'Should be closing br tag.');
+ }
+
+ /**
+ * @expectedException \QueryPath\IOException
+ */
+ public function testFailWriteXML() {
+ try {
+ qp()->writeXML('./test/no-writing.xml');
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+
+ }
+
+ /**
+ * @expectedException \QueryPath\IOException
+ */
+ public function testFailWriteXHTML() {
+ try {
+ qp()->writeXHTML('./test/no-writing.xml');
+ }
+ catch (\QueryPath\IOException $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+
+ }
+
+ /**
+ * @expectedException \QueryPath\IOException
+ */
+ public function testFailWriteHTML() {
+ try {
+ qp(' ')->writeXML('./test/no-writing.xml');
+ }
+ catch (\QueryPath\IOException $e) {
+ // print $e->getMessage();
+ throw $e;
+ }
+
+ }
+
+ public function testWriteHTML() {
+ $xml = 'foo bar';
+
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xml, 'tml')->writeHTML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ // We expect a doctype declaration at the top.
+ $this->assertEquals('foo
+
+ bar';
+
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xml, 'tml')->writeHTML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ // We expect a doctype declaration at the top.
+ $this->assertEquals('foo
+
+ bar';
+
+ if (!ob_start()) die ("Could not start OB.");
+ qp($xml, 'tml')->writeHTML();
+ $out = ob_get_contents();
+ ob_end_clean();
+
+ // We expect a doctype declaration at the top.
+ $this->assertEquals('writeXML($name);
+ $this->assertTrue(file_exists($name));
+ $this->assertTrue(qp($name) instanceof DOMQuery);
+ unlink($name);
+ }
+
+ public function testText() {
+ $xml = 'Text A
Text B
';
+ $this->assertEquals('Text AText B', qp($xml)->text());
+ $this->assertEquals('Foo', qp($xml, 'div')->eq(0)->text('Foo')->text());
+ $this->assertEquals('BarBar', qp($xml, 'div')->text('Bar')->text());
+ }
+
+ public function testTextAfter() {
+ $xml = ' After After2
After3 ';
+ $this->assertEquals('AfterAfter2', qp($xml, 'br')->textAfter());
+ $this->assertEquals('Blarg', qp($xml, 'foo')->textAfter('Blarg')->top('foo')->textAfter());
+ }
+
+ public function testTextBefore() {
+ $xml = 'Before Before2 Before3
';
+ $this->assertEquals('BeforeBefore2', qp($xml, 'br')->textBefore());
+ $this->assertEquals('Blarg', qp($xml, 'foo')->textBefore('Blarg')->top('foo')->textBefore());
+
+ }
+
+ public function testTextImplode() {
+ $xml = 'Text A
Text B
';
+ $this->assertEquals('Text A, Text B', qp($xml, 'div')->textImplode());
+ $this->assertEquals('Text A--Text B', qp($xml, 'div')->textImplode('--'));
+
+ $xml = 'Text A
Text B
';
+ $this->assertEquals('Text A , Text B', qp($xml, 'div')->textImplode());
+
+ $xml = 'Text A
+
+
Text B
';
+ $this->assertEquals('Text A , Text B', qp($xml, 'div')->textImplode(', ', TRUE));
+
+ // Test with empties
+ $xml = 'Text A
Text B
';
+ $this->assertEquals('Text A- -Text B', qp($xml, 'div')->textImplode('-', FALSE));
+ }
+
+ public function testChildrenText() {
+ $xml = '
+ NOT ME!
+ Text A
+
+
Text B
';
+ $this->assertEquals('Text A , Text B', qp($xml, 'div')->childrenText(', ', TRUE), 'Just inner text.');
+ }
+
+ public function testNext() {
+ $file = DATA_FILE;
+ $this->assertEquals('inner', qp($file, 'unary')->next()->tag());
+ $this->assertEquals('foot', qp($file, 'inner')->next()->eq(1)->tag());
+
+ $this->assertEquals('foot', qp($file, 'unary')->next('foot')->tag());
+
+ // Regression test for issue eabrand identified:
+
+ $qp = qp(\QueryPath::HTML_STUB, 'body')->append('
Hello
Goodbye
')
+ ->children('p')
+ ->after('new paragraph
');
+
+ $testarray = array('new paragraph', 'Goodbye', 'new paragraph');
+
+ //throw new Exception($qp->top()->xml());
+
+ $qp = $qp->top('p:first-of-type');
+ $this->assertEquals('Hello', $qp->text(), "Test First P " . $qp->top()->html());
+ $i = 0;
+ while($qp->next('p')->html() != null) {
+ $qp = $qp->next('p');
+ $this->assertEquals(1, count($qp));
+ $this->assertEquals($testarray[$i], $qp->text(), $i . " didn't match " . $qp->top()->xml() );
+ $i++;
+ }
+ $this->assertEquals(3, $i);
+// $this->assertEquals('new paragraph', $qp->next()->text(), "Test Newly Added P");
+// $this->assertEquals('Goodbye', $qp->next()->text(), "Test third P");
+// $this->assertEquals('new paragraph', $qp->next()->text(), "Test Other Newly Added P");
+ }
+ public function testPrev() {
+ $file = DATA_FILE;
+ $this->assertEquals('head', qp($file, 'unary')->prev()->tag());
+ $this->assertEquals('inner', qp($file, 'inner')->prev()->eq(1)->tag());
+ $this->assertEquals('head', qp($file, 'foot')->prev('head')->tag());
+ }
+ public function testNextAll() {
+ $file = DATA_FILE;
+ $this->assertEquals(3, qp($file, '#one')->nextAll()->size());
+ $this->assertEquals(2, qp($file, 'unary')->nextAll('inner')->size());
+ }
+ public function testPrevAll() {
+ $file = DATA_FILE;
+ $this->assertEquals(3, qp($file, '#four')->prevAll()->size());
+ $this->assertEquals(2, qp($file, 'foot')->prevAll('inner')->size());
+ }
+ public function testParent() {
+ $file = DATA_FILE;
+ $this->assertEquals('root', qp($file, 'unary')->parent()->tag());
+ $this->assertEquals('root', qp($file, 'li')->parent('root')->tag());
+ $this->assertEquals(2, qp($file, 'li')->parent()->size());
+ }
+ public function testClosest() {
+ $file = DATA_FILE;
+ $this->assertEquals('root', qp($file, 'li')->parent('root')->tag());
+
+ $xml = '
+
+
+
+
+
+ ';
+ $this->assertEquals(2, qp($xml, 'b')->closest('.foo')->size());
+ }
+
+ public function testParents() {
+ $file = DATA_FILE;
+
+ // Three: two inners and a root.
+ $this->assertEquals(3, qp($file, 'li')->parents()->size());
+ $this->assertEquals('root', qp($file, 'li')->parents('root')->tag());
+ }
+
+ public function testCloneAll() {
+ $file = DATA_FILE;
+
+ // Shallow test
+ $qp = qp($file, 'unary');
+ $one = $qp->get(0);
+ $two = $qp->cloneAll()->get(0);
+ $this->assertTrue($one !== $two);
+ $this->assertEquals('unary', $two->tagName);
+
+ // Deep test: make sure children are also cloned.
+ $qp = qp($file, 'inner');
+ $one = $qp->find('li')->get(0);
+ $two = $qp->top('inner')->cloneAll(TRUE)->findInPlace('li')->get(0);
+ $this->assertEquals('li', $two->tagName);
+ $this->assertTrue($one !== $two);
+ }
+
+ public function testBranch() {
+ $qp = qp(\QueryPath::HTML_STUB);
+ $branch = $qp->branch();
+ $branch->top('title')->text('Title');
+ $qp->top('title')->text('FOOOOO')->top();
+ $qp->find('body')->text('This is the body');
+
+ $this->assertEquals($qp->top('title')->text(), $branch->top('title')->text(), $branch->top()->html());
+
+ $qp = qp(\QueryPath::HTML_STUB);
+ $branch = $qp->branch('title');
+ $branch->find('title')->text('Title');
+ $qp->find('body')->text('This is the body');
+ $this->assertEquals($qp->top()->find('title')->text(), $branch->text());
+ }
+
+ public function testXpath() {
+ $file = DATA_FILE;
+
+ $this->assertEquals('head', qp($file)->xpath("//*[@id='head']")->tag());
+ }
+
+ public function test__clone() {
+ $file = DATA_FILE;
+
+ $qp = qp($file, 'inner:first-of-type');
+ $qp2 = clone $qp;
+ $this->assertFalse($qp === $qp2);
+ $qp2->findInPlace('li')->attr('foo', 'bar');
+ $this->assertEquals('', $qp->find('li')->attr('foo'));
+ $this->assertEquals('bar', $qp2->attr('foo'), $qp2->top()->xml());
+ }
+
+ public function testStub() {
+ $this->assertEquals(1, qp(\QueryPath::HTML_STUB)->find('title')->size());
+ }
+
+ public function testIterator() {
+
+ $qp = qp(\QueryPath::HTML_STUB, 'body')->append(' ');
+
+ $this->assertEquals(4, $qp->find('li')->size());
+ $i = 0;
+ foreach ($qp->find('li') as $li) {
+ ++$i;
+ $li->text('foo');
+ }
+ $this->assertEquals(4, $i);
+ $this->assertEquals('foofoofoofoo', $qp->top()->find('li')->text());
+ }
+
+ public function testModeratelySizedDocument() {
+
+ $this->assertEquals(1, qp(MEDIUM_FILE)->size());
+
+ $contents = file_get_contents(MEDIUM_FILE);
+ $this->assertEquals(1, qp($contents)->size());
+ }
+
+ /**
+ * @deprecated
+ */
+ public function testSize() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'li');
+ $this->assertEquals(5, $qp->size());
+ }
+
+ public function testCount() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'li');
+ $this->assertEquals(5, $qp->count());
+
+ // Test that this is exposed to PHP's Countable logic.
+ $this->assertEquals(5, count(qp($file, 'li')));
+
+ }
+
+ public function testLength() {
+
+ // Test that the length attribute works exactly the same as size.
+ $file = DATA_FILE;
+ $qp = qp($file, 'li');
+ $this->assertEquals(5, $qp->length);
+
+
+ }
+
+ public function testDocument() {
+ $file = DATA_FILE;
+ $doc1 = new \DOMDocument('1.0');
+ $doc1->load($file);
+ $qp = qp($doc1);
+
+ $this->assertEquals($doc1, $qp->document());
+
+ // Ensure that adding to the DOMDocument is accessible to QP:
+ $ele = $doc1->createElement('testDocument');
+ $doc1->documentElement->appendChild($ele);
+
+ $this->assertEquals(1, $qp->find('testDocument')->size());
+ }
+
+ /*
+ public function test__get() {
+ // Test that other properties are not interferred with by __get().
+ $file = DATA_FILE;
+ $options = array('QueryPath_class' => 'QueryPathExtended');
+ $foo = qp($file,'li', $options)->foo;
+
+ $this->assertEquals('bar', $foo);
+ }
+ */
+
+ /**
+ * @ expectedException \QueryPath\Exception
+ */
+ /*
+ public function testFailed__get() {
+ // This should generate an error because 'last' is protected.
+ qp(DATA_FILE)->last;
+ }
+ */
+
+ public function testDetach() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'li');
+ $start = $qp->size();
+ $finish = $qp->detach()->size();
+ $this->assertEquals($start, $finish);
+ $this->assertEquals(0, $qp->find(':root li')->size());
+ }
+
+ public function testAttach() {
+ $file = DATA_FILE;
+ $qp = qp($file, 'li');
+ $start = $qp->size();
+ $finish = $qp->detach()->size();
+ $dest = qp(' ', 'dest');
+ $qp = $qp->attach($dest);
+ $this->assertEquals(5, $dest->find(':root li')->size());
+ }
+
+ public function testEmptyElement() {
+ $file = DATA_FILE;
+ $this->assertEquals(0, qp($file, '#inner-two')->emptyElement()->find('li')->size());
+ $this->assertEquals(' ', qp($file, '#inner-two')->emptyElement()->html());
+
+ // Make sure text children get wiped out, too.
+ $this->assertEquals('', qp($file, 'foot')->emptyElement()->text());
+ }
+
+ public function testHas() {
+ $file = DATA_FILE;
+
+ // Test with DOMNode object
+ $qp = qp($file, 'foot');
+ $selector = $qp->get(0);
+ $qp = $qp->top('root')->has($selector);
+
+ // This should have one element named 'root'.
+ $this->assertEquals(1, $qp->size(), 'One element is a parent of foot');
+ $this->assertEquals('root', $qp->tag(), 'Root has foot.');
+
+ // Test with CSS selector
+ $qp = qp($file, 'root')->has('foot');
+
+ // This should have one element named 'root'.
+ $this->assertEquals(1, $qp->size(), 'One element is a parent of foot');
+ $this->assertEquals('root', $qp->tag(), 'Root has foot.');
+
+ // Test multiple matches.
+ $qp = qp($file, '#docRoot, #inner-two')->has('#five');
+ $this->assertEquals(2, $qp->size(), 'Two elements are parents of #five');
+ $this->assertEquals('inner', $qp->get(0)->tagName, 'Inner has li.');
+
+ /*
+ $this->assertEquals(qp($file, '#one')->children()->get(), qp($file, '#inner-one')->has($selector)->get(), "Both should be empty/false");
+ $qp = qp($file, 'root')->children("inner");
+ $selector = qp($file, '#two');
+ $this->assertNotEquals(qp($file, '#head'), qp($file, '#inner-one')->has($selector));
+ $this->assertEquals(qp($file, 'root'), qp($file, 'root')->has($selector), "Should both have 1 element - root");
+ */
+ }
+
+ public function testNextUntil() {
+ $file = DATA_FILE;
+ $this->assertEquals(3, qp($file, '#one')->nextUntil()->size());
+ $this->assertEquals(2, qp($file, 'li')->nextUntil('#three')->size());
+ }
+
+ public function testPrevUntil() {
+ $file = DATA_FILE;
+ $this->assertEquals(3, qp($file, '#four')->prevUntil()->size());
+ $this->assertEquals(2, qp($file, 'foot')->prevUntil('unary')->size());
+ }
+
+ public function testEven() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file, 'inner')->even()->size());
+ $this->assertEquals(2, qp($file, 'li')->even()->size());
+ }
+
+ public function testOdd() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file, 'inner')->odd()->size());
+ $this->assertEquals(3, qp($file, 'li')->odd()->size());
+ }
+
+ public function testFirst() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file, 'inner')->first()->size());
+ $this->assertEquals(1, qp($file, 'li')->first()->size());
+ $this->assertEquals("Hello", qp($file, 'li')->first()->text());
+ }
+
+ public function testFirstChild() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file, '#inner-one')->firstChild()->size());
+ $this->assertEquals("Hello", qp($file, '#inner-one')->firstChild()->text());
+ }
+
+ public function testLast() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file, 'inner')->last()->size());
+ $this->assertEquals(1, qp($file, 'li')->last()->size());
+ $this->assertEquals('', qp($file, 'li')->last()->text());
+ }
+
+ public function testLastChild() {
+ $file = DATA_FILE;
+ $this->assertEquals(1, qp($file, '#inner-one')->lastChild()->size());
+ $this->assertEquals("Last", qp($file, '#inner-one')->lastChild()->text());
+ }
+
+ public function testParentsUntil() {
+ $file = DATA_FILE;
+
+ // Three: two inners and a root.
+ $this->assertEquals(3, qp($file, 'li')->parentsUntil()->size());
+ $this->assertEquals(2, qp($file, 'li')->parentsUntil('root')->size());
+ }
+
+ public function testSort() {
+ $xml = '1 5 2 1 ';
+
+ // Canary.
+ $qp = qp($xml, 'i');
+ $expect = array(1, 5, 2, 1);
+ foreach($qp as $item) {
+ $this->assertEquals(array_shift($expect), $item->text());
+ }
+
+ // Test simple ordering.
+ $comp = function (\DOMNode $a, \DOMNode $b) {
+ if ($a->textContent == $b->textContent) {
+ return 0;
+ }
+ return $a->textContent > $b->textContent ? 1 : -1;
+ };
+ $qp = qp($xml, 'i')->sort($comp);
+ $expect = array(1, 1, 2, 5);
+ foreach($qp as $item) {
+ $this->assertEquals(array_shift($expect), $item->text());
+ }
+
+ $comp = function (\DOMNode $a, \DOMNode $b) {
+ $qpa = qp($a);
+ $qpb = qp($b);
+
+ if ($qpa->text() == $qpb->text()) {
+ return 0;
+ }
+ return $qpa->text()> $qpb->text()? 1 : -1;
+ };
+ $qp = qp($xml, 'i')->sort($comp);
+ $expect = array(1, 1, 2, 5);
+ foreach($qp as $item) {
+ $this->assertEquals(array_shift($expect), $item->text());
+ }
+
+ // Test DOM re-ordering
+ $comp = function (\DOMNode $a, \DOMNode $b) {
+ if ($a->textContent == $b->textContent) {
+ return 0;
+ }
+ return $a->textContent > $b->textContent ? 1 : -1;
+ };
+ $qp = qp($xml, 'i')->sort($comp, TRUE);
+ $expect = array(1, 1, 2, 5);
+ foreach($qp as $item) {
+ $this->assertEquals(array_shift($expect), $item->text());
+ }
+ $res = $qp->top()->xml();
+ $expect_xml = '1 1 2 5 ';
+ $this->assertXmlStringEqualsXmlString($expect_xml, $res);
+ }
+
+ /**
+ * Regression test for issue #14.
+ */
+ public function testRegressionFindOptimizations() {
+ $xml = '
+ -
+
-
+
- Test
+
+
+ ';
+
+ // From inside, should not be able to find outside.
+ $this->assertEquals(0, qp($xml, '#inside')->find('#outside')->size());
+
+ $xml = '
+ -
+
-
+
- Test
+
+
+ ';
+ // From inside, should not be able to find outside.
+ $this->assertEquals(0, qp($xml, '.inside')->find('.outside')->size());
+ }
+
+ public function testDataURL() {
+
+ $text = 'Hi!'; // Base-64 encoded value would be SGkh
+ $xml = ' ';
+
+ $qp = qp($xml, 'item')->dataURL('secret', $text, 'text/plain');
+
+ $this->assertEquals(1, $qp->top('item[secret]')->size(), 'One attr should be added.');
+
+ $this->assertEquals('data:text/plain;base64,SGkh', $qp->attr('secret'), 'Attr value should be data URL.');
+
+ $result = $qp->dataURL('secret');
+ $this->assertEquals(2, count($result), 'Should return two-array.');
+ $this->assertEquals($text, $result['data'] , 'Should return original data, decoded.');
+ $this->assertEquals('text/plain', $result['mime'], 'Should return the original MIME');
+ }
+
+ public function testEncodeDataURL() {
+ $data = \QueryPath::encodeDataURL('Hi!', 'text/plain');
+ $this->assertEquals('data:text/plain;base64,SGkh', $data);
+ }
+}
+
+/**
+ * A simple mock for testing qp()'s abstract factory.
+ *
+ * @ingroup querypath_tests
+ */
+class QueryPathExtended extends DOMQuery {
+ public $foo = 'bar';
+ public function foonator() {
+ return TRUE;
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/EntitiesTest.php b/lib/querypath/test/Tests/QueryPath/EntitiesTest.php
new file mode 100644
index 0000000..62a3426
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/EntitiesTest.php
@@ -0,0 +1,54 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+require_once __DIR__ . '/TestCase.php';
+
+/**
+ * @ingroup querypath_tests
+ */
+class EntitiesTest extends TestCase {
+ public function testReplaceEntity() {
+ $entity = 'amp';
+ $this->assertEquals('38', \QueryPath\Entities::replaceEntity($entity));
+
+ $entity = 'lceil';
+ $this->assertEquals('8968', \QueryPath\Entities::replaceEntity($entity));
+ }
+
+ public function testReplaceAllEntities() {
+ $test = '&©&& nothing. ';
+ $expect = '&©&& nothing. ';
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+
+ $test = '&&& ';
+ $expect = '&&& ';
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+
+ $test = "é\n";
+ $expect = "é\n";
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+ }
+
+ public function testReplaceHexEntities() {
+ $test = '©';
+ $expect = '©';
+ $this->assertEquals($expect, \QueryPath\Entities::replaceAllEntities($test));
+ }
+
+ public function testQPEntityReplacement() {
+ $test = '&©&& nothing. ';
+ /*$expect = '&©&& nothing. ';*/
+ // We get this because the DOM serializer re-converts entities.
+ $expect = '
+&©&& nothing. ';
+
+ $qp = qp($test, NULL, array('replace_entities' => TRUE));
+ // Interestingly, the XML serializer converts decimal to hex and ampersands
+ // to &.
+ $this->assertEquals($expect, trim($qp->xml()));
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/ExtensionTest.php b/lib/querypath/test/Tests/QueryPath/ExtensionTest.php
new file mode 100644
index 0000000..5f98612
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/ExtensionTest.php
@@ -0,0 +1,153 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/TestCase.php';
+require_once __DIR__ . '/../../../src/QueryPath/Extension.php';
+//require_once __DIR__ . '/../../../src/QueryPath.php';
+//require_once 'QueryPathTest.php';
+
+use \QueryPath\Extension;
+use \QueryPath\ExtensionRegistry;
+
+/**
+ *
+ */
+//define('self::DATA_FILE', 'test/data.xml');
+
+/**
+ * Run all of the usual tests, plus some extras, with some extensions loaded.
+ * @ingroup querypath_tests
+ * @group extension
+ */
+class QueryPathExtensionTest extends TestCase {
+
+ public static function setUpBeforeClass() {
+ ExtensionRegistry::extend('\QueryPath\Tests\StubExtensionOne');
+ ExtensionRegistry::extend('\QueryPath\Tests\StubExtensionTwo');
+ }
+
+ public function testExtensions() {
+ $this->assertNotNull(qp());
+ }
+
+ public function testHasExtension() {
+ $this->assertTrue(ExtensionRegistry::hasExtension('\QueryPath\Tests\StubExtensionOne'));
+ }
+
+ public function testStubToe() {
+ $this->assertEquals(1, qp(self::DATA_FILE, 'unary')->stubToe()->top(':root > toe')->size());
+ }
+
+ public function testStuble() {
+ $this->assertEquals('arg1arg2', qp(self::DATA_FILE)->stuble('arg1', 'arg2'));
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testNoRegistry() {
+ ExtensionRegistry::$useRegistry = FALSE;
+ try {
+ qp(self::DATA_FILE)->stuble('arg1', 'arg2');
+ }
+ catch (\QueryPath\Exception $e) {
+ ExtensionRegistry::$useRegistry = TRUE;
+ throw $e;
+ }
+
+ }
+
+ public function testExtend() {
+ $this->assertFalse(ExtensionRegistry::hasExtension('\QueryPath\Tests\StubExtensionThree'));
+ ExtensionRegistry::extend('\QueryPath\Tests\StubExtensionThree');
+ $this->assertTrue(ExtensionRegistry::hasExtension('\QueryPath\Tests\StubExtensionThree'));
+ }
+
+ public function tearDown() {
+ ExtensionRegistry::$useRegistry = TRUE;
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testAutoloadExtensions() {
+ // FIXME: This isn't really much of a test.
+ ExtensionRegistry::autoloadExtensions(FALSE);
+ try {
+ qp()->stubToe();
+ }
+ catch (Exception $e) {
+ ExtensionRegistry::autoloadExtensions(TRUE);
+ throw $e;
+ }
+ }
+
+ /**
+ * @expectedException \QueryPath\Exception
+ */
+ public function testCallFailure() {
+ qp()->foo();
+ }
+
+ // This does not (and will not) throw an exception.
+ // /**
+ // * @expectedException QueryPathException
+ // */
+ // public function testExtendNoSuchClass() {
+ // ExtensionRegistry::extend('StubExtensionFour');
+ // }
+
+}
+// Create a stub extension:
+/**
+ * Create a stub extension
+ *
+ * @ingroup querypath_tests
+ */
+class StubExtensionOne implements Extension {
+ private $qp = NULL;
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+
+ public function stubToe() {
+ $this->qp->top()->append(' ')->end();
+ return $this->qp;
+ }
+}
+/**
+ * Create a stub extension
+ *
+ * @ingroup querypath_tests
+ */
+class StubExtensionTwo implements Extension {
+ private $qp = NULL;
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+ public function stuble($arg1, $arg2) {
+ return $arg1 . $arg2;
+ }
+}
+/**
+ * Create a stub extension
+ *
+ * @ingroup querypath_tests
+ */
+class StubExtensionThree implements Extension {
+ private $qp = NULL;
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+ public function stuble($arg1, $arg2) {
+ return $arg1 . $arg2;
+ }
+}
+
+//ExtensionRegistry::extend('StubExtensionOne');
+//ExtensionRegistry::extend('StubExtensionTwo');
diff --git a/lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php b/lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php
new file mode 100644
index 0000000..266003e
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/Extensions/QPXMLTest.php
@@ -0,0 +1,41 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/../TestCase.php';
+require_once 'src/QueryPath/Extension/QPXML.php';
+/**
+ * @ingroup querypath_tests
+ * @group extension
+ */
+class QPXMLTests extends TestCase {
+
+ protected $file = './test/advanced.xml';
+ public static function setUpBeforeClass() {
+ \QueryPath::enable('\QueryPath\Extension\QPXML');
+ }
+
+ public function testCDATA() {
+ $this->assertEquals('This is a CDATA section.', qp($this->file, 'first')->cdata());
+
+ $msg = 'Another CDATA Section';
+ $this->assertEquals($msg, qp($this->file, 'second')->cdata($msg)->top()->find('second')->cdata());
+ }
+
+ public function testComment(){
+ $this->assertEquals('This is a comment.', trim(qp($this->file, 'root')->comment()));
+ $msg = "Message";
+ $this->assertEquals($msg, qp($this->file, 'second')->comment($msg)->top()->find('second')->comment());
+ }
+
+ public function testProcessingInstruction() {
+ $this->assertEquals('This is a processing instruction.', trim(qp($this->file, 'third')->pi()));
+ $msg = "Message";
+ $this->assertEquals($msg, qp($this->file, 'second')->pi('qp', $msg)->top()->find('second')->pi());
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php b/lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php
new file mode 100644
index 0000000..f0c5ed1
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/Extensions/QPXSLTest.php
@@ -0,0 +1,60 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+
+namespace QueryPath\Tests;
+
+//require_once 'PHPUnit/Autoload.php';
+require_once 'src/QueryPath/Extension/QPXSL.php';
+require_once __DIR__ . '/../TestCase.php';
+/**
+ * @ingroup querypath_tests
+ * @extension
+ */
+class QPXSLTests extends TestCase {
+
+ protected $file = './test/advanced.xml';
+
+ public static function setUpBeforeClass() {
+ \QueryPath::enable('\QueryPath\Extension\QPXSL');
+ }
+ public function testXSLT() {
+ // XML and XSLT taken from http://us.php.net/manual/en/xsl.examples-collection.php
+ // and then modified to be *actually welformed* XML.
+ $orig = '
+
+ Fight for your mind
+ Ben Harper
+ 1995
+
+
+ Electric Ladyland
+ Jimi Hendrix
+ 1997
+
+ ';
+
+ $template = '
+
+
+
+
+ Hey! Welcome to \'s sweet CD collection!
+
+
+
+
+
+ by -
+
+
+
+ ';
+
+ $qp = qp($orig)->xslt($template);
+ $this->assertEquals(2, $qp->top('h1')->size(), 'Make sure that data was formatted');
+ }
+}
diff --git a/lib/querypath/test/Tests/QueryPath/OptionsTest.php b/lib/querypath/test/Tests/QueryPath/OptionsTest.php
new file mode 100644
index 0000000..d2efbc9
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/OptionsTest.php
@@ -0,0 +1,60 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+namespace QueryPath\Tests;
+require_once __DIR__ . '/TestCase.php';
+use \QueryPath\Options;
+
+/**
+ * @ingroup querypath_tests
+ */
+class OptionsTest extends TestCase {
+
+ public function testOptions() {
+ $expect = array('test1' => 'val1', 'test2' => 'val2');
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+
+ Options::set($options);
+
+ $results = Options::get();
+ $this->assertEquals($expect, $results);
+
+ $this->assertEquals('val1', $results['test1']);
+ }
+
+ public function testQPOverrideOrder() {
+ $expect = array('test1' => 'val3', 'test2' => 'val2');
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+
+ Options::set($options);
+ $qpOpts = qp(NULL, NULL, array('test1'=>'val3', 'replace_entities' => TRUE))->getOptions();
+
+ $this->assertEquals($expect['test1'], $qpOpts['test1']);
+ $this->assertEquals(TRUE, $qpOpts['replace_entities']);
+ $this->assertNull($qpOpts['parser_flags']);
+ $this->assertEquals($expect['test2'], $qpOpts['test2']);
+ }
+
+ public function testQPHas() {
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+
+ Options::set($options);
+ $this->assertTrue(Options::has('test1'));
+ $this->assertFalse(Options::has('test3'));
+ }
+ public function testQPMerge() {
+ $options = array('test1' => 'val1', 'test2' => 'val2');
+ $options2 = array('test1' => 'val3', 'test4' => 'val4');
+
+ Options::set($options);
+ Options::merge($options2);
+
+ $results = Options::get();
+ $this->assertTrue(Options::has('test4'));
+ $this->assertEquals('val3', $results['test1']);
+ }
+
+}
diff --git a/lib/querypath/test/Tests/QueryPath/QueryPathTest.php b/lib/querypath/test/Tests/QueryPath/QueryPathTest.php
new file mode 100644
index 0000000..71fd225
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/QueryPathTest.php
@@ -0,0 +1,56 @@
+assertInstanceOf('\QueryPath\DOMQuery', $qp);
+
+ }
+
+ public function testWithHTML() {
+ $qp = \QueryPath::with(\QueryPath::HTML_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+ }
+ public function testWithHTML5() {
+ $qp = \QueryPath::withHTML5(\QueryPath::HTML5_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+ }
+
+ public function testWithXML() {
+ $qp = \QueryPath::with(\QueryPath::XHTML_STUB);
+
+ $this->assertInstanceOf('\QueryPath\DOMQuery', $qp);
+ }
+
+ public function testEnable() {
+ \QueryPath::enable('\QueryPath\Tests\DummyExtension');
+
+ $qp = \QueryPath::with(\QueryPath::XHTML_STUB);
+
+ $this->assertTrue($qp->grrrrrrr());
+
+ }
+
+}
+
+class DummyExtension implements \QueryPath\Extension {
+
+ public function __construct(\QueryPath\Query $qp) {
+ $this->qp = $qp;
+ }
+
+ public function grrrrrrr() {
+ return TRUE;
+ }
+
+}
diff --git a/lib/querypath/test/Tests/QueryPath/TestCase.php b/lib/querypath/test/Tests/QueryPath/TestCase.php
new file mode 100644
index 0000000..7e212b0
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/TestCase.php
@@ -0,0 +1,22 @@
+
+ * @license The GNU Lesser GPL (LGPL) or an MIT-like license.
+ */
+
+namespace QueryPath\Tests;
+
+/** @addtogroup querypath_tests Tests
+ * Unit tests and regression tests for QueryPath.
+ */
+
+/** */
+//require_once 'PHPUnit/Autoload.php';
+require_once __DIR__ . '/TestCase.php';
+
+/**
+ * Test the XMLish functions of QueryPath.
+ *
+ * This uses a testing harness, XMLishMock, to test
+ * a protected method of QueryPath.
+ *
+ * @ingroup querypath_test
+ */
+class XMLishTest extends TestCase {
+ public function testXMLishMock() {
+ $tests = array(
+ 'this/is/a/path' => FALSE,
+ "this is just some plain\ntext with a line break." => FALSE,
+ '2 > 1' => FALSE,
+ '1 < 2' => FALSE,
+ //'1 < 2 > 1' => FALSE,
+ ' ' => TRUE,
+ ' ' => TRUE,
+ ' ' => TRUE, // It's not valid, but HTML parser will try it.
+ );
+ foreach ($tests as $test => $correct) {
+ $mock = new XMLishMock();
+ $this->assertEquals($correct, $mock->exposedIsXMLish($test), "Testing $test");
+ }
+ }
+
+ public function testXMLishWithBrokenHTML() {
+ $html = ' Located in a natural bowl north of 10th Avenue, Rosenbloom Field was made possible by a gift from Virginia Whitney Rosenbloom \'36 and Abe H. Rosenbloom \'34. The Pioneers observed the occasion of the field\'s dedication on Oct. 4, 1975, by defeating Carleton 36-26. Rosenbloom Field has a seating capacity of 1,500. A former member of the Grinnell Advisory Board and other college committees, Abe Rosenbloom played football at Grinnell from 1931 to 1933. He played guard and was one of the Missouri Valley Conference\'s smallest gridders (5\'6" and 170 pounds). He averaged more than 45 minutes a game playing time during a 24-game varsity career and was named to the Des Moines Register\'s all-Missouri Valley Conference squad in 1932 and 1933. On the south side of the field, a memorial recalls the 100th anniversary of the first intercollegiate football game played west of the Mississippi. The game took place on the Grinnell campus on Nov. 16, 1889. On the north side, a marker commemorates the first 50 years of football in the west, and recalls the same game, played in 1889, Grinnell College vs. the University of Iowa. Grinnell won, 24-0.
';
+ $mock = new XMLishMock();
+ $this->assertEquals(TRUE, $mock->exposedIsXMLish($html), "Testing broken HTML");
+ }
+
+}
+
+/**
+ * A testing class for XMLish tests.
+ *
+ * @ingroup querypath_tests
+ */
+class XMLishMock extends \QueryPath\DOMQuery {
+ public function exposedIsXMLish($str) {
+ return $this->isXMLish($str);
+ }
+}
--
cgit v1.2.3