summaryrefslogtreecommitdiff
path: root/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php')
-rw-r--r--lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php520
1 files changed, 520 insertions, 0 deletions
diff --git a/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php b/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php
new file mode 100644
index 0000000..981ddd1
--- /dev/null
+++ b/lib/querypath/test/Tests/QueryPath/CSS/ParserTest.php
@@ -0,0 +1,520 @@
+<?php
+/**
+ * @file
+ * CSS Event handling tests
+ */
+namespace QueryPath\Tests;
+
+require_once __DIR__ . '/../TestCase.php';
+
+use \QueryPath\CSS\Token;
+use \QueryPath\CSS\QueryPathEventHandler;
+use \QueryPath\CSS\Parser;
+use \QueryPath\CSS\EventHandler;
+
+/**
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class ParserTest extends TestCase {
+
+ private function getMockHandler($method) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array($method));
+ $mock->expects($this->once())
+ ->method($method)
+ ->with($this->equalTo('mytest'));
+ return $mock;
+ }
+
+ public function testElementID() {
+ $mock = $this->getMockHandler('elementID');
+ $parser = new Parser('#mytest', $mock);
+ $parser->parse();
+
+ }
+
+ public function testElement() {
+
+ // Without namespace
+ $mock = $this->getMockHandler('element');
+ $parser = new Parser('mytest', $mock);
+ $parser->parse();
+
+ // With empty namespace
+ $mock = $this->getMockHandler('element');
+ $parser = new Parser('|mytest', $mock);
+ $parser->parse();
+ }
+
+ public function testElementNS() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementNS'));
+ $mock->expects($this->once())
+ ->method('elementNS')
+ ->with($this->equalTo('mytest'), $this->equalTo('myns'));
+
+ $parser = new Parser('myns|mytest', $mock);
+ $parser->parse();
+
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementNS'));
+ $mock->expects($this->once())
+ ->method('elementNS')
+ ->with($this->equalTo('mytest'), $this->equalTo('*'));
+
+ $parser = new Parser('*|mytest', $mock);
+ $parser->parse();
+
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElementInNS'));
+ $mock->expects($this->once())
+ ->method('anyElementInNS')
+ ->with($this->equalTo('*'));
+
+ $parser = new Parser('*|*', $mock);
+ $parser->parse();
+ }
+
+ public function testAnyElement() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElement', 'element'));
+ $mock->expects($this->once())
+ ->method('anyElement');
+
+ $parser = new Parser('*', $mock);
+ $parser->parse();
+ }
+
+ public function testAnyElementInNS() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyElementInNS', 'element'));
+ $mock->expects($this->once())
+ ->method('anyElementInNS')
+ ->with($this->equalTo('myns'));
+
+ $parser = new Parser('myns|*', $mock);
+ $parser->parse();
+ }
+
+ public function testElementClass() {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('elementClass'));
+ $mock->expects($this->once())
+ ->method('elementClass')
+ ->with($this->equalTo('myclass'));
+
+ $parser = new Parser('.myclass', $mock);
+ $parser->parse();
+ }
+
+ public function testPseudoClass() {
+
+ // Test empty pseudoclass
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass'));
+ $mock->expects($this->once())
+ ->method('pseudoClass')
+ ->with($this->equalTo('mypclass'));
+
+ $parser = new Parser('myele:mypclass', $mock);
+ $parser->parse();
+
+ // Test pseudoclass with value
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass'));
+ $mock->expects($this->once())
+ ->method('pseudoClass')
+ ->with($this->equalTo('mypclass'), $this->equalTo('myval'));
+
+ $parser = new Parser('myele:mypclass(myval)', $mock);
+ $parser->parse();
+
+ // Test pseudclass with pseudoclass:
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoClass'));
+ $mock->expects($this->once())
+ ->method('pseudoClass')
+ ->with($this->equalTo('mypclass'), $this->equalTo(':anotherPseudo'));
+
+ $parser = new Parser('myele:mypclass(:anotherPseudo)', $mock);
+ $parser->parse();
+
+ }
+
+ public function testPseudoElement() {
+ // Test pseudo-element
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('pseudoElement'));
+ $mock->expects($this->once())
+ ->method('pseudoElement')
+ ->with($this->equalTo('mypele'));
+
+ $parser = new Parser('myele::mypele', $mock);
+ $parser->parse();
+ }
+
+ public function testDirectDescendant() {
+ // Test direct Descendant
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('directDescendant'));
+ $mock->expects($this->once())
+ ->method('directDescendant');
+
+ $parser = new Parser('ele1 > ele2', $mock);
+ $parser->parse();
+
+ }
+
+ public function testAnyDescendant() {
+ // Test direct Descendant
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anyDescendant'));
+ $mock->expects($this->once())
+ ->method('anyDescendant');
+
+ $parser = new Parser('ele1 .class', $mock);
+ $parser->parse();
+
+ }
+
+ public function testAdjacent() {
+ // Test sibling
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('adjacent'));
+ $mock->expects($this->once())
+ ->method('adjacent');
+
+ $parser = new Parser('ele1 + ele2', $mock);
+ $parser->parse();
+ }
+
+ public function testSibling() {
+ // Test adjacent
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('sibling'));
+ $mock->expects($this->once())
+ ->method('sibling');
+
+ $parser = new Parser('ele1 ~ ele2', $mock);
+ $parser->parse();
+ }
+
+ public function testAnotherSelector() {
+ // Test adjacent
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('anotherSelector'));
+ $mock->expects($this->once())
+ ->method('anotherSelector');
+
+ $parser = new Parser('ele1 , ele2', $mock);
+ $parser->parse();
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalAttribute() {
+
+ // Note that this is designed to test throwError() as well as
+ // bad selector handling.
+
+ $parser = new Parser('[test=~far]', new TestEventHandler());
+ try {
+ $parser->parse();
+ }
+ catch (Exception $e) {
+ //print $e->getMessage();
+ throw $e;
+ }
+ }
+
+ public function testAttribute() {
+ $selectors = array(
+ 'element[attr]' => 'attr',
+ '*[attr]' => 'attr',
+ 'element[attr]:class' => 'attr',
+ 'element[attr2]' => 'attr2', // Issue #
+ );
+ foreach ($selectors as $filter => $expected) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attribute'));
+ $mock->expects($this->once())
+ ->method('attribute')
+ ->with($this->equalTo($expected));
+
+ $parser = new Parser($filter, $mock);
+ $parser->parse();
+ }
+
+ $selectors = array(
+ '*[attr="value"]' => array('attr','value',EventHandler::isExactly),
+ '*[attr^="value"]' => array('attr','value',EventHandler::beginsWith),
+ '*[attr$="value"]' => array('attr','value',EventHandler::endsWith),
+ '*[attr*="value"]' => array('attr','value',EventHandler::containsInString),
+ '*[attr~="value"]' => array('attr','value',EventHandler::containsWithSpace),
+ '*[attr|="value"]' => array('attr','value',EventHandler::containsWithHyphen),
+
+ // This should act like [attr="value"]
+ '*[|attr="value"]' => array('attr', 'value', EventHandler::isExactly),
+
+ // This behavior is displayed in the spec, but not accounted for in the
+ // grammar:
+ '*[attr=value]' => array('attr','value',EventHandler::isExactly),
+
+ // Should be able to escape chars using backslash.
+ '*[attr="\.value"]' => array('attr','.value',EventHandler::isExactly),
+ '*[attr="\.\]\]\]"]' => array('attr','.]]]',EventHandler::isExactly),
+
+ // Backslash-backslash should resolve to single backslash.
+ '*[attr="\\\c"]' => array('attr','\\c',EventHandler::isExactly),
+
+ // Should return an empty value. It seems, though, that a value should be
+ // passed here.
+ '*[attr=""]' => array('attr','',EventHandler::isExactly),
+ );
+ foreach ($selectors as $filter => $expected) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attribute'));
+ $mock->expects($this->once())
+ ->method('attribute')
+ ->with($this->equalTo($expected[0]), $this->equalTo($expected[1]), $this->equalTo($expected[2]));
+
+ $parser = new Parser($filter, $mock);
+ $parser->parse();
+ }
+ }
+
+ public function testAttributeNS() {
+ $selectors = array(
+ '*[ns|attr="value"]' => array('attr', 'ns', 'value',EventHandler::isExactly),
+ '*[*|attr^="value"]' => array('attr', '*', 'value',EventHandler::beginsWith),
+ '*[*|attr|="value"]' => array('attr', '*', 'value',EventHandler::containsWithHyphen),
+ );
+
+ foreach ($selectors as $filter => $expected) {
+ $mock = $this->getMock('\QueryPath\Tests\TestEventHandler', array('attributeNS'));
+ $mock->expects($this->once())
+ ->method('attributeNS')
+ ->with($this->equalTo($expected[0]), $this->equalTo($expected[1]), $this->equalTo($expected[2]), $this->equalTo($expected[3]));
+
+ $parser = new Parser($filter, $mock);
+ $parser->parse();
+ }
+ }
+
+ // Test things that should break...
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalCombinators1() {
+ $handler = new TestEventHandler();
+ $parser = new Parser('ele1 > > ele2', $handler);
+ $parser->parse();
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalCombinators2() {
+ $handler = new TestEventHandler();
+ $parser = new Parser('ele1+ ,ele2', $handler);
+ $parser->parse();
+ }
+
+ /**
+ * @expectedException \QueryPath\CSS\ParseException
+ */
+ public function testIllegalID() {
+ $handler = new TestEventHandler();
+ $parser = new Parser('##ID', $handler);
+ $parser->parse();
+ }
+
+ // Test combinations
+
+ public function testElementNSClassAndAttribute() {
+
+ $expect = array(
+ new TestEvent(TestEvent::elementNS, 'element', 'ns'),
+ new TestEvent(TestEvent::elementClass, 'class'),
+ new TestEvent(TestEvent::attribute, 'name', 'value', EventHandler::isExactly),
+ );
+ $selector = 'ns|element.class[name="value"]';
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+ $this->assertTrue($handler->success());
+
+ // Again, with spaces this time:
+ $selector = ' ns|element. class[ name = "value" ]';
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+
+ //$handler->dumpStack();
+ $this->assertTrue($handler->success());
+ }
+
+ public function testAllCombo() {
+
+ $selector = '*|ele1 > ele2.class1 + ns1|ele3.class2[attr=simple] ~
+ .class2[attr2~="longer string of text."]:pseudoClass(value)
+ .class3::pseudoElement';
+ $expect = array(
+ new TestEvent(TestEvent::elementNS, 'ele1', '*'),
+ new TestEvent(TestEvent::directDescendant),
+ new TestEvent(TestEvent::element, 'ele2'),
+ new TestEvent(TestEvent::elementClass, 'class1'),
+ new TestEvent(TestEvent::adjacent),
+ new TestEvent(TestEvent::elementNS, 'ele3', 'ns1'),
+ new TestEvent(TestEvent::elementClass, 'class2'),
+ new TestEvent(TestEvent::attribute, 'attr', 'simple', EventHandler::isExactly),
+ new TestEvent(TestEvent::sibling),
+ new TestEvent(TestEvent::elementClass, 'class2'),
+ new TestEvent(TestEvent::attribute, 'attr2', 'longer string of text.', EventHandler::containsWithSpace),
+ new TestEvent(TestEvent::pseudoClass, 'pseudoClass', 'value'),
+ new TestEvent(TestEvent::anyDescendant),
+ new TestEvent(TestEvent::elementClass, 'class3'),
+ new TestEvent(TestEvent::pseudoElement, 'pseudoElement'),
+ );
+
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+
+ //$handler->dumpStack();
+
+ $this->assertTrue($handler->success());
+
+ /*
+ // Again, with spaces this time:
+ $selector = ' *|ele1 > ele2. class1 + ns1|ele3. class2[ attr=simple] ~ .class2[attr2 ~= "longer string of text."]:pseudoClass(value) .class3::pseudoElement';
+
+ $handler = new TestEventHandler();
+ $handler->expects($expect);
+ $parser = new Parser($selector, $handler);
+ $parser->parse();
+
+ $handler->dumpStack();
+ $this->assertTrue($handler->success());
+ */
+ }
+}
+
+/**
+ * Testing harness for the EventHandler.
+ *
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class TestEventHandler implements EventHandler {
+ var $stack = NULL;
+ var $expect = array();
+
+ public function __construct() {
+ $this->stack = array();
+ }
+
+ public function getStack() {
+ return $this->stack;
+ }
+
+ public function dumpStack() {
+ print "\nExpected:\n";
+ $format = "Element %d: %s\n";
+ foreach ($this->expect as $item) {
+ printf($format, $item->eventType(), implode(',', $item->params()));
+ }
+
+ print "Got:\n";
+ foreach($this->stack as $item){
+ printf($format, $item->eventType(), implode(',', $item->params()));
+ }
+ }
+
+ public function expects($stack) {
+ $this->expect = $stack;
+ }
+
+ public function success() {
+ return ($this->expect == $this->stack);
+ }
+
+ public function elementID($id) {
+ $this->stack[] = new TestEvent(TestEvent::elementID, $id);
+ }
+ public function element($name) {
+ $this->stack[] = new TestEvent(TestEvent::element, $name);
+ }
+ public function elementNS($name, $namespace = NULL){
+ $this->stack[] = new TestEvent(TestEvent::elementNS, $name, $namespace);
+ }
+ public function anyElement(){
+ $this->stack[] = new TestEvent(TestEvent::anyElement);
+ }
+ public function anyElementInNS($ns){
+ $this->stack[] = new TestEvent(TestEvent::anyElementInNS, $ns);
+ }
+ public function elementClass($name){
+ $this->stack[] = new TestEvent(TestEvent::elementClass, $name);
+ }
+ public function attribute($name, $value = NULL, $operation = EventHandler::isExactly){
+ $this->stack[] = new TestEvent(TestEvent::attribute, $name, $value, $operation);
+ }
+ public function attributeNS($name, $ns, $value = NULL, $operation = EventHandler::isExactly){
+ $this->stack[] = new TestEvent(TestEvent::attributeNS, $name, $ns, $value, $operation);
+ }
+ public function pseudoClass($name, $value = NULL){
+ $this->stack[] = new TestEvent(TestEvent::pseudoClass, $name, $value);
+ }
+ public function pseudoElement($name){
+ $this->stack[] = new TestEvent(TestEvent::pseudoElement, $name);
+ }
+ public function directDescendant(){
+ $this->stack[] = new TestEvent(TestEvent::directDescendant);
+ }
+ public function anyDescendant() {
+ $this->stack[] = new TestEvent(TestEvent::anyDescendant);
+ }
+ public function adjacent(){
+ $this->stack[] = new TestEvent(TestEvent::adjacent);
+ }
+ public function anotherSelector(){
+ $this->stack[] = new TestEvent(TestEvent::anotherSelector);
+ }
+ public function sibling(){
+ $this->stack[] = new TestEvent(TestEvent::sibling);
+ }
+}
+
+/**
+ * Simple utility object for use with the TestEventHandler.
+ *
+ * @ingroup querypath_tests
+ * @group CSS
+ */
+class TestEvent {
+ const elementID = 0;
+ const element = 1;
+ const elementNS = 2;
+ const anyElement = 3;
+ const elementClass = 4;
+ const attribute = 5;
+ const attributeNS = 6;
+ const pseudoClass = 7;
+ const pseudoElement = 8;
+ const directDescendant = 9;
+ const adjacent = 10;
+ const anotherSelector = 11;
+ const sibling = 12;
+ const anyElementInNS = 13;
+ const anyDescendant = 14;
+
+ var $type = NULL;
+ var $params = NULL;
+
+ public function __construct($event_type) {
+ $this->type = $event_type;
+ $args = func_get_args();
+ array_shift($args);
+ $this->params = $args;
+ }
+
+ public function eventType() {
+ return $this->type;
+ }
+
+ public function params() {
+ return $this->params;
+ }
+}