diff options
author | emkael <emkael@tlen.pl> | 2017-01-18 20:07:16 +0100 |
---|---|---|
committer | emkael <emkael@tlen.pl> | 2017-01-18 20:07:16 +0100 |
commit | 9a9c04512e5dcb77c7fe5d850e3f2a0250cc160e (patch) | |
tree | fed46b5f4c2ed3a050bb1a7ad7c6d0a3ea844d55 /lib/querypath/test/Tests/QueryPath/DOMQueryTest.php | |
parent | c5bcf8f74fb80b7e163663845b0d6e35cabface3 (diff) |
* Motor Sport Magazine feed provider
Diffstat (limited to 'lib/querypath/test/Tests/QueryPath/DOMQueryTest.php')
-rw-r--r-- | lib/querypath/test/Tests/QueryPath/DOMQueryTest.php | 1865 |
1 files changed, 1865 insertions, 0 deletions
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 @@ +<?php +/** @file + * Tests for the DOMQuery class. + * + * + * @author M Butcher <matt@aleph-null.tv> + * @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 = '<?xml version="1.0" ?><root><inner/></root>'; + $qp = qp($str); + $this->assertEquals(1, count($qp->get())); + $this->assertTrue($qp->get(0) instanceof \DOMNode); + + // From SimpleXML + $str = '<?xml version="1.0" ?><root><inner/></root>'; + $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 = '<html><head></head><body><br></body></html>'; + $qp = htmlqp($borken); + $this->assertEquals(1, count($qp->get())); + $this->assertTrue($qp->get(0) instanceof \DOMNode); + + // XHTML Faker + $borken = '<?xml version="1.0"?><html><head></head><body><br></body></html>'; + $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 = '<html><head></head><body><p>' . chr(13) . '</p><div id="after"/></body></html>'; + $this->assertFalse(strpos(htmlqp($borken)->html(), ' '), 'Test that CRs are not encoded.'); + + // Regression for #58: Make sure we aren't getting encoded. + $borken = '<html><head><style> + .BlueText { + color:red; + }</style><body></body></html>'; + + $this->assertFalse(strpos(htmlqp($borken)->html(), ' '), 'Test that LF is not encoded.'); + + // Low ASCII in a file + $borken = '<html><head></head><body><p>' . chr(27) . '</p><div id="after"/></body></html>'; + $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('<test/>')->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('<?xml version="1.0" encoding="utf-8"?><test/>', 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 = '<?xml version="1.0"?><l><i/><i/><i/><i/><i/></l>'; + $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('<foo>&foonator;</foo>', NULL); + } + catch (Exception $e) { + //print $e->getMessage(); + throw $e; + } + } + + /** + * @expectedException \QueryPath\ParseException + */ + public function testFailedParseXMLElement() { + try { + qp('<?xml version="1.0"?><foo><bar>foonator;</foo>', NULL); + } + catch (Exception $e) { + //print $e->getMessage(); + throw $e; + } + } + public function testIgnoreParserWarnings() { + $qp = @qp('<html><body><b><i>BAD!</b></i></body>', NULL, array('ignore_parser_warnings' => TRUE)); + $this->assertTrue(strpos($qp->html(), '<i>BAD!</i>') !== FALSE); + + \QueryPath\Options::merge(array('ignore_parser_warnings' => TRUE)); + $qp = @qp('<html><body><b><i>BAD!</b></i></body>'); + $this->assertTrue(strpos($qp->html(), '<i>BAD!</i>') !== FALSE); + + $qp = @qp('<html><body><blarg>BAD!</blarg></body>'); + $this->assertTrue(strpos($qp->html(), '<blarg>BAD!</blarg>') !== 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('<?xml version="1.0"?><foo>&foonator;</foo>', NULL); + } + catch (Exception $e) { + //print $e->getMessage(); + throw $e; + } + } + + public function testReplaceEntitiesOption() { + $path = '<?xml version="1.0"?><root/>'; + $xml = qp($path, NULL, array('replace_entities' => TRUE))->xml('<foo>&</foo>')->xml(); + $this->assertTrue(strpos($xml, '<foo>&</foo>') !== FALSE); + + $xml = qp($path, NULL, array('replace_entities' => TRUE))->html('<foo>&</foo>')->xml(); + $this->assertTrue(strpos($xml, '<foo>&</foo>') !== FALSE); + + $xml = qp($path, NULL, array('replace_entities' => TRUE))->xhtml('<foo>&</foo>')->xml(); + $this->assertTrue(strpos($xml, '<foo>&</foo>') !== FALSE); + + \QueryPath\Options::set(array('replace_entities' => TRUE)); + $this->assertTrue(strpos($xml, '<foo>&</foo>') !== 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 = '<?xml version="1.0"?><root><a/>Test</root>'; + $qp = qp($string)->find('root'); + $this->assertEquals(1, count($qp->get()), 'Check tag.'); + $this->assertEquals($qp->get(0)->tagName, 'root'); + + $string = '<?xml version="1.0"?><root class="findme">Test</root>'; + $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 = '<?xml version="1.0"?><root><a/>Test</root>'; + $qp = qp($string)->find('root'); + $this->assertEquals(1, count($qp->get()), 'Check tag.'); + $this->assertEquals($qp->get(0)->tagName, 'root'); + + $string = '<?xml version="1.0"?><root class="findme">Test</root>'; + $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 = '<?xml version="1.0"?><root><u><l/><l/><l/></u><u/></root>'; + $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 = '<?xml version="1.0"?><root><one a1="1" a2="2" a3="3"/></root>'; + $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 = '<?xml version="1.0"?><root><div foo="bar"/></root>'; + + $this->assertFalse(qp($xml, 'root')->hasAttr('foo')); + $this->assertTrue(qp($xml, 'div')->hasAttr('foo')); + + $xml = '<?xml version="1.0"?><root><div foo="bar"/><div foo="baz"></div></root>'; + $this->assertTrue(qp($xml, 'div')->hasAttr('foo')); + + $xml = '<?xml version="1.0"?><root><div bar="bar"/><div foo="baz"></div></root>'; + $this->assertFalse(qp($xml, 'div')->hasAttr('foo')); + + $xml = '<?xml version="1.0"?><root><div bar="bar"/><div bAZ="baz"></div></root>'; + $this->assertFalse(qp($xml, 'div')->hasAttr('foo')); + } + + public function testVal() { + $qp = qp('<?xml version="1.0"?><foo><bar value="test"/></foo>', 'bar'); + $this->assertEquals('test', $qp->val()); + + $qp = qp('<?xml version="1.0"?><foo><bar/></foo>', '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 = '<?xml version="1.0"?><foo><bar id="one"/><baz id="two"/></foo>'; + $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 = '<?xml version="1.0"?><root><div id="one">Foo</div><div>Moo</div></root>'; + $qp = qp($xml, 'div')->filterPreg('/Foo/'); + + $this->assertEquals(1, $qp->Size()); + + // Check to make sure textContent is collected correctly. + $xml = '<?xml version="1.0"?><root><div>Hello <i>World</i></div></root>'; + $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 <li> 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 = '<?xml version="1.0" ?> + <root> + <one/> + <one><two/></one> + <one><two><three/></two></one> + <one><two><three><four/></three></two></one> + <one/> + <one><two><three><banana/></three></two></one> + </root>'; + $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 = '<?xml version="1.0" ?> + <root> + CDATA + </root>'; + $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('<test/>')->find(':root > unary > test')->size()); + $qp = qp($file,'#inner-one')->append('<li id="appended"/>'); + + $appended = $qp->find('#appended'); + $this->assertEquals(1, $appended->size()); + $this->assertNull($appended->get(0)->nextSibling); + + $this->assertEquals(2, qp($file, 'inner')->append('<test/>')->top()->find('test')->size()); + $this->assertEquals(2, qp($file, 'inner')->append(qp('<?xml version="1.0"?><test/>'))->top()->find('test')->size()); + $this->assertEquals(4, qp($file, 'inner')->append(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size()); + + // Issue #6: This seems to break on Debian Etch systems... no idea why. + $this->assertEquals('test', qp()->append('<test/>')->top()->tag()); + + // Issue #7: Failure issues warnings + // This seems to be working as expected -- libxml emits + // parse errors. + //$this->assertEquals(NULL, qp()->append('<test')); + + // Test loading SimpleXML. + $simp = simplexml_load_file($file); + $qp = qp('<?xml version="1.0"?><foo/>')->append($simp); + $this->assertEquals(1, $qp->find('root')->size()); + + // Test with replace entities turned on: + $qp = qp($file, 'root', array('replace_entities' => TRUE))->append('<p>»</p>'); + // 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 = '<p>' . mb_convert_encoding(chr(187), 'utf-8', 'iso-8859-1') . '</p>'; + //$this->assertEquals('<p>»</p>', $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('<foo><bar></foo>'); + } + 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('<?xml version="1.0"?><root><dest/></root>', '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('<test/>')->find(':root > unary > test')->size()); + $qp = qp($file,'#inner-one')->prepend('<li id="appended"/>')->find('#appended'); + $this->assertEquals(1, $qp->size()); + $this->assertNull($qp->get(0)->previousSibling); + + // Test repeated insert + $this->assertEquals(2, qp($file,'inner')->prepend('<test/>')->top()->find('test')->size()); + $this->assertEquals(4, qp($file,'inner')->prepend(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size()); + } + + public function testPrependTo() { + $file = DATA_FILE; + $dest = qp('<?xml version="1.0"?><root><dest/></root>', '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('<test/>')->find(':root > test ~ unary')->size()); + $this->assertEquals(1, qp($file,'unary')->before('<test/>')->top('head ~ test')->size()); + $this->assertEquals('unary', qp($file,'unary')->before('<test/>')->top(':root > test')->get(0)->nextSibling->tagName); + + // Test repeated insert + $this->assertEquals(2, qp($file,'inner')->before('<test/>')->top()->find('test')->size()); + $this->assertEquals(4, qp($file,'inner')->before(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size()); + } + + public function testAfter() { + $file = DATA_FILE; + $this->assertEquals(1, qp($file,'unary')->after('<test/>')->top(':root > unary ~ test')->size()); + $this->assertEquals('unary', qp($file,'unary')->after('<test/>')->top(':root > test')->get(0)->previousSibling->tagName); + + $this->assertEquals(2, qp($file,'inner')->after('<test/>')->top()->find('test')->size()); + $this->assertEquals(4, qp($file,'inner')->after(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test'))->top()->find('test')->size()); + } + + public function testInsertBefore() { + $file = DATA_FILE; + $dest = qp('<?xml version="1.0"?><root><dest/></root>', '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('<?xml version="1.0"?><root><dest/></root>', '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('<test><foo/></test>')->top('test'); + //print $qp->get(0)->ownerDocument->saveXML(); + $this->assertEquals(1, $qp->size()); + + $qp = qp($file,'unary')->replaceWith(qp('<?xml version="1.0"?><root><test/><test/></root>', 'test')); + //print $qp->get(0)->ownerDocument->saveXML(); + $this->assertEquals(2, $qp->top()->find('test')->size()); + } + + public function testReplaceAll() { + $qp1 = qp('<?xml version="1.0"?><root><l/><l/></root>'); + $doc = qp('<?xml version="1.0"?><bob><m/><m/></bob>')->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 = '<?xml version="1.0"?><root><wrapper><center/><junk/></wrapper></root>'; + $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 = '<?xml version="1.0"?><root><wrapper><center id="1"/><center id="2"/></wrapper></root>'; + $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 = '<?xml version="1.0"?><root><center/></root>'; + $qp = qp($xml, 'center')->unwrap(); + $this->assertEquals('center', $qp->top()->tag()); + + } + + /** + * @expectedException \QueryPath\Exception + */ + public function testFailedUnwrap() { + // Cannot unwrap the root element. + $xml = '<?xml version="1.0"?><root></root>'; + $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('<test id="testWrap"></test>')->get(0)->ownerDocument->saveXML(); + $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length); + + $xml = qp($file,'unary')->wrap(qp('<?xml version="1.0"?><root><test id="testWrap"></test><test id="ignored"></test></root>', 'test'))->get(0)->ownerDocument->saveXML(); + $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length); + + $xml = qp($file,'li')->wrap('<test class="testWrap"></test>')->get(0)->ownerDocument->saveXML(); + $this->assertEquals(5, qp($xml, '.testWrap')->size()); + + $xml = qp($file,'li')->wrap('<test class="testWrap"><inside><center/></inside></test>')->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('<test id="testWrap"></test>')->get(0)->ownerDocument->saveXML(); + $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length); + + $xml = qp($file,'unary')->wrapAll(qp('<?xml version="1.0"?><root><test id="testWrap"></test><test id="ignored"></test></root>', 'test'))->get(0)->ownerDocument->saveXML(); + $this->assertEquals(1, qp($xml, '#testWrap')->get(0)->childNodes->length); + + $xml = qp($file,'li')->wrapAll('<test class="testWrap"><inside><center/></inside></test>')->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('<test class="testWrap"></test>')->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('<test class="testWrap"></test>')->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('<?xml version="1.0"?><root><test class="testWrap"/><test class="ignored"/></root>', '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 = '<?xml version="1.0"?><root><a>test</a><b> FAIL</b></root>'; + $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 = '<html><body><div>Hello + <div>how are you + <div>fine thank you + <div>and you ?</div> + </div> + </div> + </div> + </body></html>'; + $cr = $this->contentsRecurse(qp($xml)); + $this->assertEquals(14, count($cr), implode("\n", $cr)); + } + + public function testNS() { + $xml = '<?xml version="1.0"?><root xmlns="foo:bar"><e>test</e></root>'; + + $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 = '<b>test</b>'; + $this->assertEquals($html, $qp->html($html)->find('b')->html()); + + $html = '<html><head><title>foo</title></head><body>bar</body></html>'; + // We expect a DocType to be prepended: + $this->assertEquals('<!DOCTYPE', substr(qp($html)->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('<p>Hello</p>')->html()); + $this->assertEquals('', qp($html, 'nonexistant')->html('<p>Hello</p>')->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 = '<html><head></head><body><div id="me">Test<p>Again</p></div></body></html>'; + + $this->assertEquals('Test<p>Again</p>', qp($html,'#me')->innerHTML()); + } + + public function testInnerXML() { + $html = '<?xml version="1.0"?><root><div id="me">Test<p>Again1</p></div></root>'; + $test = 'Test<p>Again1</p>'; + + $this->assertEquals($test, qp($html,'#me')->innerXML()); + + $html = '<?xml version="1.0"?><root><div id="me">Test<p>Again2<br/></p><![CDATA[Hello]]><?pi foo ?></div></root>'; + $test = 'Test<p>Again2<br/></p><![CDATA[Hello]]><?pi foo ?>'; + + $this->assertEquals($test, qp($html,'#me')->innerXML()); + + $html = '<?xml version="1.0"?><root><div id="me"/></root>'; + $test = ''; + $this->assertEquals($test, qp($html,'#me')->innerXML()); + + $html = '<?xml version="1.0"?><root id="me">test</root>'; + $test = 'test'; + $this->assertEquals($test, qp($html,'#me')->innerXML()); + } + + public function testInnerXHTML() { + $html = '<?xml version="1.0"?><html><head></head><body><div id="me">Test<p>Again</p></div></body></html>'; + + $this->assertEquals('Test<p>Again</p>', qp($html,'#me')->innerXHTML()); + + // Regression for issue #10: Tags should not be unary (e.g. we want <script></script>, not <script/>) + $xml = '<html><head><title>foo</title></head><body><div id="me">Test<p>Again<br/></p></div></body></html>'; + // Look for a closing </br> 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 = '<b>test</b>'; + $this->assertEquals($xml, $qp->xml($xml)->find('b')->xml()); + + $xml = '<html><head><title>foo</title></head><body>bar</body></html>'; + // We expect an XML declaration to be prepended: + $this->assertEquals('<?xml', substr(qp($xml, 'html')->xml(), 0, 5)); + + // We don't want an XM/L declaration if xml(TRUE). + $xml = '<?xml version="1.0"?><foo/>'; + $this->assertFalse(strpos(qp($xml)->xml(TRUE), '<?xml')); + + // We expect NULL if the document is empty. + $this->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 = '<b>test</b>'; + $this->assertEquals($xml, $qp->xml($xml)->find('b')->xhtml()); + + $xml = '<html><head><title>foo</title></head><body>bar</body></html>'; + // We expect an XML declaration to be prepended: + $this->assertEquals('<?xml', substr(qp($xml, 'html')->xhtml(), 0, 5)); + + // We don't want an XM/L declaration if xml(TRUE). + $xml = '<?xml version="1.0"?><foo/>'; + $this->assertFalse(strpos(qp($xml)->xhtml(TRUE), '<?xml')); + + // We expect NULL if the document is empty. + $this->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 <script></script>, not <script/>) + $xml = '<html><head><title>foo</title></head> + <body> + bar<br/><hr width="100"> + <script></script> + <script> + alert("Foo"); + </script> + <frameset id="fooframeset"></frameset> + </body></html>'; + + $xhtml = qp($xml)->xhtml(); + + //throw new Exception($xhtml); + + // Look for a properly formatted BR unary tag: + $regex = '/<br \/>/'; + $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.'); + + // Look for a properly formatted HR tag: + $regex = '/<hr width="100" \/>/'; + $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.'); + + // Ensure that script tag is not collapsed: + $regex = '/<script><\/script>/'; + $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.'); + + // Ensure that frameset tag is not collapsed (it looks like <frame>): + $regex = '/<frameset id="fooframeset"><\/frameset>/'; + $this->assertRegExp($regex, $xhtml, 'BR should have a closing tag.'); + + // Ensure that script gets wrapped in CDATA: + $find = '/* <![CDATA[ '; + $this->assertTrue(strpos($xhtml, $find) > 0, 'CDATA section should be escaped.'); + + // Regression: Make sure it parses. + $xhtml = qp('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head></head><body><br /></body></html>')->xhtml(); + + qp($xhtml); + + } + + public function testWriteXML() { + $xml = '<?xml version="1.0"?><html><head><title>foo</title></head><body>bar</body></html>'; + + 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('<?xml', substr($out, 0, 5)); + + $xml = '<?xml version="1.0"?><html><head><script> + <!-- + 1 < 2; + --> + </script> + <![CDATA[This is CDATA]]> + <title>foo</title></head><body>bar</body></html>'; + + 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('<?xml', substr($out, 0, 5)); + + // Test writing to a file: + $name = './' . __FUNCTION__ . '.xml'; + qp($xml)->writeXML($name); + $this->assertTrue(file_exists($name)); + $this->assertTrue(qp($name) instanceof DOMQuery); + unlink($name); + } + + public function testWriteXHTML() { + $xml = '<?xml version="1.0"?><html><head><title>foo</title></head><body>bar</body></html>'; + + 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('<?xml', substr($out, 0, 5)); + + $xml = '<?xml version="1.0"?><html><head><script> + <!-- + 1 < 2; + --> + </script> + <![CDATA[This is CDATA]]> + <title>foo</title></head><body>bar</body></html>'; + + 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('<?xml', substr($out, 0, 5)); + + // Test writing to a file: + $name = './' . __FUNCTION__ . '.xml'; + qp($xml)->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 = '<?xml version="1.0"?><html><head><title>foo</title><script></script><br/></head><body>bar</body></html>'; + 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('<?xml version="1.0"?><foo/>')->writeXML('./test/no-writing.xml'); + } + catch (\QueryPath\IOException $e) { + // print $e->getMessage(); + throw $e; + } + + } + + public function testWriteHTML() { + $xml = '<html><head><title>foo</title></head><body>bar</body></html>'; + + 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('<!DOC', substr($out, 0, 5)); + + $xml = '<html><head><title>foo</title> + <script><!-- + var foo = 1 < 5; + --></script> + </head><body>bar</body></html>'; + + 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('<!DOC', substr($out, 0, 5)); + + $xml = '<html><head><title>foo</title> + <script><![CDATA[ + var foo = 1 < 5; + ]]></script> + </head><body>bar</body></html>'; + + 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('<!DOC', substr($out, 0, 5)); + + // Test writing to a file: + $name = './' . __FUNCTION__ . '.html'; + qp($xml)->writeXML($name); + $this->assertTrue(file_exists($name)); + $this->assertTrue(qp($name) instanceof DOMQuery); + unlink($name); + } + + public function testText() { + $xml = '<?xml version="1.0"?><root><div>Text A</div><div>Text B</div></root>'; + $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 = '<?xml version="1.0"?><root><br/>After<foo/><br/>After2<div/>After3</root>'; + $this->assertEquals('AfterAfter2', qp($xml, 'br')->textAfter()); + $this->assertEquals('Blarg', qp($xml, 'foo')->textAfter('Blarg')->top('foo')->textAfter()); + } + + public function testTextBefore() { + $xml = '<?xml version="1.0"?><root>Before<br/><foo/>Before2<br/>Before3<div/></root>'; + $this->assertEquals('BeforeBefore2', qp($xml, 'br')->textBefore()); + $this->assertEquals('Blarg', qp($xml, 'foo')->textBefore('Blarg')->top('foo')->textBefore()); + + } + + public function testTextImplode() { + $xml = '<?xml version="1.0"?><root><div>Text A</div><div>Text B</div></root>'; + $this->assertEquals('Text A, Text B', qp($xml, 'div')->textImplode()); + $this->assertEquals('Text A--Text B', qp($xml, 'div')->textImplode('--')); + + $xml = '<?xml version="1.0"?><root><div>Text A </div><div>Text B</div></root>'; + $this->assertEquals('Text A , Text B', qp($xml, 'div')->textImplode()); + + $xml = '<?xml version="1.0"?><root><div>Text A </div> + <div> + </div><div>Text B</div></root>'; + $this->assertEquals('Text A , Text B', qp($xml, 'div')->textImplode(', ', TRUE)); + + // Test with empties + $xml = '<?xml version="1.0"?><root><div>Text A</div><div> </div><div>Text B</div></root>'; + $this->assertEquals('Text A- -Text B', qp($xml, 'div')->textImplode('-', FALSE)); + } + + public function testChildrenText() { + $xml = '<?xml version="1.0"?><root><wrapper> + NOT ME! + <div>Text A </div> + <div> + </div><div>Text B</div></wrapper></root>'; + $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('<div></div><p>Hello</p><p>Goodbye</p>') + ->children('p') + ->after('<p>new paragraph</p>'); + + $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 = '<?xml version="1.0"?> + <root> + <a class="foo"> + <b/> + </a> + <b class="foo"/> + </root>'; + $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('<li/><li/><li/><li/>'); + + $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('<?xml version="1.0"?><root><dest/></root>', '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('<inner id="inner-two"/>', 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 = '<?xml version="1.0"?><r><s/><i>1</i><i>5</i><i>2</i><i>1</i><e/></r>'; + + // 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 = '<?xml version="1.0"?><r><s/><i>1</i><i>1</i><i>2</i><i>5</i><e/></r>'; + $this->assertXmlStringEqualsXmlString($expect_xml, $res); + } + + /** + * Regression test for issue #14. + */ + public function testRegressionFindOptimizations() { + $xml = '<?xml version="1.0"?><root> + <item id="outside"> + <item> + <item id="inside">Test</item> + </item> + </item> + </root>'; + + // From inside, should not be able to find outside. + $this->assertEquals(0, qp($xml, '#inside')->find('#outside')->size()); + + $xml = '<?xml version="1.0"?><root> + <item class="outside"> + <item> + <item class="inside">Test</item> + </item> + </item> + </root>'; + // 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 = '<?xml version="1.0"?><root><item/></root>'; + + $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; + } +} |