-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
172 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
|
||
/** | ||
* @copyright Copyright (C) Ibexa AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Ibexa\Behat\Browser\Locator\Validator; | ||
|
||
use Ibexa\Behat\Browser\Locator\CSSLocator; | ||
use Symfony\Component\CssSelector\Exception\ParseException; | ||
use Symfony\Component\CssSelector\Node\CombinedSelectorNode; | ||
use Symfony\Component\CssSelector\Node\ElementNode; | ||
use Symfony\Component\CssSelector\Node\FunctionNode; | ||
use Symfony\Component\CssSelector\Node\SelectorNode; | ||
use Symfony\Component\CssSelector\Parser\Parser; | ||
|
||
class CssLocatorValidator | ||
{ | ||
private Parser $parser; | ||
|
||
private array $partiallySupportedPseudoClasses = ['first-of-type', 'last-of-type', 'nth-of-type', 'nth-last-of-type']; | ||
|
||
public function __construct() | ||
{ | ||
$this->parser = new Parser(); | ||
} | ||
|
||
public function validate(CSSLocator $cssLocator): void | ||
{ | ||
if (!$this->isValidSelector($cssLocator->getSelector())) { | ||
throw new ParseException( | ||
sprintf( | ||
"Locator '%s' with ID '%s' cannot be used because of limitations of the CssSelector component. See more: https://symfony.com/doc/current/components/css_selector.html#limitations-of-the-cssselector-component", | ||
$cssLocator->getSelector(), | ||
$cssLocator->getIdentifier() | ||
) | ||
); | ||
} | ||
} | ||
|
||
private function isValidSelector(string $selector): bool | ||
{ | ||
$tokens = $this->parser->parse($selector); | ||
|
||
while (!empty($tokens)) { | ||
$token = array_pop($tokens); | ||
|
||
if ($token instanceof SelectorNode) { | ||
array_push($tokens, $token->getTree()); | ||
} | ||
if ($token instanceof CombinedSelectorNode) { | ||
array_push($tokens, $token->getSelector(), $token->getSubSelector()); | ||
} | ||
|
||
if ($token instanceof FunctionNode) { | ||
if (!$this->isValidFunctionNode($token)) { | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private function isValidFunctionNode(FunctionNode $node): bool | ||
{ | ||
if (!in_array($node->getName(), $this->partiallySupportedPseudoClasses)) { | ||
return true; | ||
} | ||
|
||
$selector = $node->getSelector(); | ||
|
||
if (!($selector instanceof ElementNode)) { | ||
$selector = $selector->getSelector(); | ||
} | ||
|
||
if ($selector->getElement() === null) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
tests/Browser/Locator/Validator/CssLocatorValidatorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<?php | ||
|
||
/** | ||
* @copyright Copyright (C) Ibexa AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Ibexa\Tests\Behat\Browser\Locator\Validator; | ||
|
||
use Ibexa\Behat\Browser\Locator\CSSLocator; | ||
use Ibexa\Behat\Browser\Locator\Validator\CssLocatorValidator; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\CssSelector\Exception\ParseException; | ||
|
||
class CssLocatorValidatorTest extends TestCase | ||
{ | ||
private CssLocatorValidator $validator; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->validator = new CssLocatorValidator(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideValidSelectors | ||
*/ | ||
public function testValidationPasses(string $selector): void | ||
{ | ||
$this->validator->validate(new CSSLocator('test', $selector)); | ||
$this->expectNotToPerformAssertions(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidSelectors | ||
*/ | ||
public function testValidationFails(string $selector): void | ||
{ | ||
$this->expectException(ParseException::class); | ||
$this->expectExceptionMessage( | ||
sprintf( | ||
"Locator '%s' with ID 'test' cannot be used because of limitations of the CssSelector component. See more: https://symfony.com/doc/current/components/css_selector.html#limitations-of-the-cssselector-component", | ||
$selector | ||
) | ||
); | ||
$this->validator->validate(new CSSLocator('test', $selector)); | ||
} | ||
|
||
public static function provideValidSelectors(): iterable | ||
{ | ||
return [ | ||
['div'], | ||
['div:nth-of-type(1)'], | ||
['div.ibexa-content-field:nth-of-type(3)'], | ||
['selector1 section:nth-of-type(2)'], | ||
['tr td:nth-of-type(2)'], | ||
['[data-id="test"] div.ibexa-field-edit:nth-of-type(3)'], | ||
['div.ibexa-details__item:nth-of-type(2) .ibexa-details__item-content'], | ||
['.ibexa-version-compare__field-wrapper div.ibexa-content-field:nth-of-type(1) .ibexa-content-field__name'], | ||
['div.ibexa-dropdown__wrapper > ul.ibexa-dropdown__selection-info > li:nth-child(1)'], | ||
['.ibexa-dropdown__item:nth-child(3)'], | ||
['.nav-item:nth-child(2) .nav-link'], | ||
]; | ||
} | ||
|
||
public static function provideInvalidSelectors(): iterable | ||
{ | ||
return [ | ||
['.ibexa:nth-of-type(1)'], | ||
['.ibexa-table__cell:nth-of-type(5),td:nth-of-type(5)'], | ||
['.ibexa-available-field-types__list > li:not(.ibexa-available-field-type--hidden) .ibexa-available-field-type__content:nth-of-type(5)'], | ||
['.c-finder-branch:nth-of-type(3) .c-finder-leaf'], | ||
['.selector .ibexa:nth-of-type(2)'], | ||
['.tab-pane.active .ibexa-fieldgroup:nth-of-type(2)'], | ||
['div.ibexa-ca-company-tab-company-profile__top-wrapper .ibexa-details__items .ibexa-details__item:nth-of-type(3) .ibexa-details__item-content'], | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters