Skip to content

Commit

Permalink
add DOMAssertionTrait & unit tets
Browse files Browse the repository at this point in the history
  • Loading branch information
bkdotcom committed Sep 18, 2023
1 parent fde934f commit 9545731
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 151 deletions.
23 changes: 5 additions & 18 deletions src/CssXpath.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static function cssToXpath($selector)
}

$xpath = self::processRegexs($selector);
$xpath = \preg_match('/^\/\//', $xpath)
$xpath = \preg_match('/^\//', $xpath)
? $xpath
: '//' . $xpath;
$xpath = \preg_replace('#/{4}#', '', $xpath);
Expand Down Expand Up @@ -144,6 +144,10 @@ private static function regexs()
self::$strings[] = ($matches[1] ? '*' : '') . '[not(' . $xpathNot . ')]';
return '[{' . (\count(self::$strings) - 1) . '}]';
}),
array('/([\s]?):has\((.*?)\)/', static function ($matches) {
self::$strings[] = '[count(' . self::cssToXpath($matches[2]) . ') > 0]';
return '[{' . (\count(self::$strings) - 1) . '}]';
}),
// All blocks of 2 or more spaces
array('/\s{2,}/', static function () {
return ' ';
Expand Down Expand Up @@ -237,23 +241,6 @@ static function ($matches) {
array('/:scope/', static function () {
return '//';
}),
// The Relational Pseudo-class: :has()
// https://www.w3.org/TR/selectors-4/#has-pseudo
// E! : https://www.w3.org/TR/selectors4/
array('/^.+!.+$/', static function ($matches) {
$subSelectors = \explode(',', $matches[0]);
foreach ($subSelectors as $i => $subSelector) {
$parts = \explode('!', $subSelector);
$subSelector = \array_shift($parts);
if (\preg_match_all('/((?:[^\/]*\/?\/?)|$)/', $parts[0], $matches)) {
$results = $matches[0];
$results[] = \str_repeat('/..', \count($results) - 2);
$subSelector .= \implode('', $results);
}
$subSelectors[$i] = $subSelector;
}
return \implode(',', $subSelectors);
}),
// Restore strings
array('/\[\{(\d+)\}\]/', static function ($matches) {
return self::$strings[$matches[1]];
Expand Down
146 changes: 146 additions & 0 deletions src/DOMAssertionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
/**
* This file is part of CssXpath
*
* @package CssXPath
* @author Brad Kent <[email protected]>
* @license http://opensource.org/licenses/MIT MIT
* @copyright 2018 Brad Kent
* @version 1.0
*
* @link http://www.github.com/bkdotcom/CssXpath
*/

namespace bdk\CssXpath;

/**
* PHPUnit DOM Assertions.
*
* These assertions were provide with PHPUnit 3.3 - < 5
*/
trait DOMAssertionTrait
{
/**
* Assert the presence, absence, or count of elements in a document matching
* the CSS $selector, regardless of the contents of those elements.
*
* The first argument, $selector, is the CSS selector used to match
* the elements in the $actual document.
*
* The second argument, $count, can be either boolean or numeric.
* When boolean, it asserts for presence of elements matching the selector
* (true) or absence of elements (false).
* When numeric, it assertsk the count of elements.
*
* examples:
* assertSelectCount("#binder", true, $xml); // any?
* assertSelectCount(".binder", 3, $xml); // exactly 3?
*
* @param array $selector CSS selector
* @param int|bool|array $count bool, count, or array('>'=5, <=10)
* @param mixed $actual HTML
* @param string $message exception message
* @param bool $isHtml not used
*
* @return void
*
* @link(https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertSelectCount
*/
public static function assertSelectCount($selector, $count, $actual, $message = '', $isHtml = true)
{
self::assertSelectEquals($selector, true, $count, $actual, $message, $isHtml);
}

/**
* Reports an error identified by $message
* if the CSS selector $selector does not match $count elements
* in the DOMNode $actual with the value $content.
*
* $count can be one of the following types:
* bool: Asserts for presence of elements matching the selector (TRUE) or absence of elements (FALSE).
* int: Asserts the count of elements.
* array: Asserts that the count is in a range specified by using <, >, <=, and >= as keys.
*
* Examples
* assertSelectEquals("#binder .name", "Chuck", true, $xml); // any?
* assertSelectEquals("#binder .name", "Chuck", false, $xml); // none?
*
* @param string $selector css selector
* @param string $content content to match against. may specify regex as regexp:/regexp/
* @param int|bool|array $count bool, integer, or array('>' => 5, '<=' => 10)
* @param mixed $actual html or domdocument
* @param string $message exception message
* @param bool $isHtml not used
*
* @return void
* @throws \PHPUnit\Framework\Exception Invalid count format.
*
* @link https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertSelectEquals
*/
public static function assertSelectEquals($selector, $content, $count, $actual, $message = '', $isHtml = true)
{
$found = CssSelect::select($actual, $selector);
if (\is_string($content)) {
foreach ($found as $k => $node) {
$keep = $content === ''
? $node['innerHTML'] === ''
: \strstr($node['innerHTML'], $content) !== false;
if (\preg_match('/^regexp\s*:\s*(.*)/i', $content, $matches)) {
$keep = (bool) \preg_match($matches[1], $node['innerHTML']);
}
if (!$keep) {
unset($found[$k]);
}
}
}
$countFound = \count($found);
if (\is_numeric($count)) {
self::assertEquals($count, $countFound, $message);
return;
}
if (\is_bool($count)) {
$isFound = \count($found) > 0;
$count
? self::assertTrue($isFound, $message)
: self::assertFalse($isFound, $message);
return;
}
if (\is_array($count) && \array_intersect_key($count, \array_flip(array('>', '<', '>=', '<=')))) {
if (isset($count['>'])) {
self::assertTrue($countFound > $count['>'], $message);
}
if (isset($count['>='])) {
self::assertTrue($countFound >= $count['>='], $message);
}
if (isset($count['<'])) {
self::assertTrue($countFound < $count['<'], $message);
}
if (isset($count['<='])) {
self::assertTrue($countFound <= $count['<='], $message);
}
return;
}
throw new \PHPUnit\Framework\Exception('Invalid count format');
}

/**
* examples:
* assertSelectRegExp("#binder .name", "/Mike|Derek/", true, $xml); // any?
* assertSelectRegExp("#binder .name", "/Mike|Derek/", 3, $xml); // 3?
*
* @param array $selector CSS selector
* @param string $pattern regex
* @param int|bool|array $count bool, count, or array('>'=5, <=10)
* @param mixed $actual HTML or domdocument
* @param string $message exception message
* @param bool $isHtml not used
*
* @return void
*
* @link( https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertSelectRegExp, link)
*/
public static function assertSelectRegExp($selector, $pattern, $count, $actual, $message = '', $isHtml = true)
{
self::assertSelectEquals($selector, 'regexp:' . $pattern, $count, $actual, $message, $isHtml);
}
}
132 changes: 5 additions & 127 deletions src/DOMTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,134 +13,12 @@

namespace bdk\CssXpath;

use PHPUnit\Framework\TestCase;

/**
* PHPUnit DOM Assertions.
*
* These assertions were provide with PHPUnit 3.3 - < 5
* TestCase with DOM Assertions.
*/
abstract class DOMTestCase extends \PHPUnit\Framework\TestCase
abstract class DOMTestCase extends TestCase
{
/**
* Assert the presence, absence, or count of elements in a document matching
* the CSS $selector, regardless of the contents of those elements.
*
* The first argument, $selector, is the CSS selector used to match
* the elements in the $actual document.
*
* The second argument, $count, can be either boolean or numeric.
* When boolean, it asserts for presence of elements matching the selector
* (true) or absence of elements (false).
* When numeric, it assertsk the count of elements.
*
* examples:
* assertSelectCount("#binder", true, $xml); // any?
* assertSelectCount(".binder", 3, $xml); // exactly 3?
*
* @param array $selector CSS selector
* @param integer|boolean|array $count bool, count, or array('>'=5, <=10)
* @param mixed $actual HTML
* @param string $message exception message
* @param boolean $isHtml not used
*
* @return void
*
* @link(https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertSelectCount
*/
public static function assertSelectCount($selector, $count, $actual, $message = '', $isHtml = true)
{
self::assertSelectEquals($selector, true, $count, $actual, $message, $isHtml);
}

/**
* Reports an error identified by $message
* if the CSS selector $selector does not match $count elements
* in the DOMNode $actual with the value $content.
*
* $count can be one of the following types:
* boolean: Asserts for presence of elements matching the selector (TRUE) or absence of elements (FALSE).
* integer: Asserts the count of elements.
* array: Asserts that the count is in a range specified by using <, >, <=, and >= as keys.
*
* Examples
* assertSelectEquals("#binder .name", "Chuck", true, $xml); // any?
* assertSelectEquals("#binder .name", "Chuck", false, $xml); // none?
*
* @param string $selector css selector
* @param string $content content to match against. may specify regex as regexp:/regexp/
* @param integer|boolean|array $count bool, integer, or array('>' => 5, '<=' => 10)
* @param mixed $actual html or domdocument
* @param string $message exception message
* @param boolean $isHtml not used
*
* @return void
* @throws \PHPUnit\Framework\Exception Invalid count format.
*
* @link https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertSelectEquals
*/
public static function assertSelectEquals($selector, $content, $count, $actual, $message = '', $isHtml = true)
{
$found = CssSelect::select($actual, $selector);
if (is_string($content)) {
foreach ($found as $k => $node) {
$keep = true;
if ($content === '') {
$keep = $node['innerHTML'] === '';
} elseif (preg_match('/^regexp\s*:\s*(.*)/i', $content, $matches)) {
$keep = (bool) preg_match($matches[1], $node['innerHTML']);
} else {
$keep = strstr($node['innerHTML'], $content) !== false;
}
if (!$keep) {
unset($found[$k]);
}
}
}
$countFound = count($found);
if (is_numeric($count)) {
self::assertEquals($count, $countFound, $message);
} elseif (is_bool($count)) {
$isFound = $found > 0;
if ($count) {
self::assertTrue($isFound, $message);
} else {
self::assertFalse($isFound, $message);
}
} elseif (is_array($count) && array_intersect_key($count, array_flip(array('>','<','>=','<=')))) {
if (isset($count['>'])) {
self::assertTrue($countFound > $count['>'], $message);
}
if (isset($count['>='])) {
self::assertTrue($countFound >= $count['>='], $message);
}
if (isset($count['<'])) {
self::assertTrue($countFound < $count['<'], $message);
}
if (isset($count['<='])) {
self::assertTrue($countFound <= $count['<='], $message);
}
} else {
throw new \PHPUnit\Framework\Exception('Invalid count format');
}
}

/**
* examples:
* assertSelectRegExp("#binder .name", "/Mike|Derek/", true, $xml); // any?
* assertSelectRegExp("#binder .name", "/Mike|Derek/", 3, $xml); // 3?
*
* @param array $selector CSS selector
* @param string $pattern regex
* @param integer|boolean|array $count bool, count, or array('>'=5, <=10)
* @param mixed $actual HTML or domdocument
* @param string $message exception message
* @param boolean $isHtml not used
*
* @return void
*
* @link( https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertSelectRegExp, link)
*/
public static function assertSelectRegExp($selector, $pattern, $count, $actual, $message = '', $isHtml = true)
{
self::assertSelectEquals($selector, "regexp:$pattern", $count, $actual, $message, $isHtml);
}
use DOMAssertionTrait;
}
6 changes: 3 additions & 3 deletions tests/CssXpath/CssSelectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function selectProvider()
array('li:nth-last-child(3)', 1),
array('li:nth-last-child(4)', 1),
array('li:nth-last-child(6)', 0),
array('ul li! > a', 1),
// array('ul li! > a', 1),
array(':scope ul li > a', 1),
array('.a', 2),
array('#article', 1),
Expand All @@ -66,7 +66,7 @@ public function selectProvider()
array('ul > li:last-child [href]', 1),
array('[class~=large] li[class~=a]', 2),
array('li[class]:not(.bar)', 1),
array(':header', 1),
array('div > :header', 1),
array('.bar.a', 1),
array('bo $ us', 0),
);
Expand Down Expand Up @@ -99,7 +99,7 @@ public function testSelectStatic($selector, $count, $inner = null)
<span class="classa"><span class="classb">hi</span></span>
HTML;
$found = CssSelect::select($html, $selector);
self::assertSame($count, \count($found));
self::assertSame($count, \count($found), $selector);

if ($inner !== null) {
self::assertSame($inner, $found[0]['innerHTML']);
Expand Down
14 changes: 11 additions & 3 deletions tests/CssXpath/CssXpathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ public function cssToXpathProvider()
array('foo[id=bar]', '//foo[@id="bar"]'),
array('[style=color: red; border: 1px solid black;]', '//*[@style="color: red; border: 1px solid black;"]'),
array('foo[style=color: red; border: 1px solid black;]', '//foo[@style="color: red; border: 1px solid black;"]'),
// array(':button', '//input[@type="button"]'),
// array(':submit', '//input[@type="submit"]'),
array('textarea', '//textarea'),
array(':first-child', '//*[1]'),
array('div:first-child', '//div[1]'),
array(':last-child', '//*[last()]'),
Expand All @@ -67,6 +64,17 @@ public function cssToXpathProvider()
# https://github.com/tj/php-selector/issues/14
array('.classa > .classb', '//*[contains(concat(" ", normalize-space(@class), " "), " classa ")]/*[contains(concat(" ", normalize-space(@class), " "), " classb ")]'),
array('ul > li:first-child', '//ul/li[1]'),
array(':button', '//*[self::button or @type="button"]'),
array(':submit', '//*[@type="submit" or (self::button and not(@type))]'),
array(':input', '//*[self::input or self::select or self::textarea or self::button]'),
array('textarea', '//textarea'),
array('input:password', '//input[@type="password"]'),
array(':header', '//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]'),
array(':autocomplete', '//[@autocomplete="on"]'),
array(':autofocus', '//[@autofocus]'),
array(':scope', ''),
array('div:not(".thing")', '//div[not(//"*[contains(concat(" ", normalize-space(@class), " "), " thing ")]")]'),
array('a:has(> img)', '//a[count(/img) > 0]'),
);
}

Expand Down
Loading

0 comments on commit 9545731

Please sign in to comment.