├── .phpstan-sources.neon ├── .gitignore ├── .phpstan-tests.neon ├── phpunit.xml ├── sources ├── Lib │ ├── Base │ │ ├── ComponentInterface.php │ │ └── Component.php │ └── MarkupValidator │ │ ├── MarkupProviderInterface.php │ │ ├── MarkupValidatorInterface.php │ │ ├── MessagePrinterInterface.php │ │ ├── MessageFilterInterface.php │ │ ├── MarkupValidatorMessageInterface.php │ │ ├── DefaultMessagePrinter.php │ │ ├── DefaultMarkupProvider.php │ │ ├── W3CMarkupValidatorMessage.php │ │ ├── W3CMarkupValidator.php │ │ ├── DefaultMessageFilter.php │ │ └── MarkupValidatorMessage.php └── Module │ └── MarkupValidator.php ├── .php-cs-fixer.dist.php ├── tests ├── Base │ └── TestCase.php ├── Lib │ ├── Base │ │ └── ComponentTest.php │ └── MarkupValidator │ │ ├── DefaultMarkupProviderTest.php │ │ ├── W3CMarkupValidatorTest.php │ │ ├── MarkupValidatorMessageTest.php │ │ ├── W3CMarkupValidatorMessageTest.php │ │ ├── DefaultMessagePrinterTest.php │ │ └── DefaultMessageFilterTest.php └── Module │ └── MarkupValidatorTest.php ├── composer.json ├── .github └── workflows │ └── build.yml ├── README.md └── LICENSE.txt /.phpstan-sources.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 5 3 | paths: 4 | - "sources" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.php-cs-fixer.cache 2 | /.phpunit.cache 3 | /build/ 4 | /composer.lock 5 | /vendor/ 6 | -------------------------------------------------------------------------------- /.phpstan-tests.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 1 3 | paths: 4 | - "tests" 5 | ignoreErrors: 6 | - '#Call to an undefined method Kolyunya\\Codeception\\Tests\\Lib\\MarkupValidator\\DefaultMessageFilterTest::assertArraySubset#' 7 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | tests 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sources/Lib/Base/ComponentInterface.php: -------------------------------------------------------------------------------- 1 | in(sprintf('%s/sources', __DIR__)) 8 | ->in(sprintf('%s/tests', __DIR__)) 9 | ->name('*.php') 10 | ->files() 11 | ; 12 | 13 | $config = (new Config()) 14 | ->setRules(array( 15 | '@PSR1' => true, 16 | '@PSR2' => true, 17 | 'array_syntax' => array( 18 | 'syntax' => 'long', 19 | ), 20 | 'no_trailing_whitespace' => true, 21 | 'ordered_imports' => array( 22 | 'imports_order' => null, 23 | ), 24 | 'single_blank_line_at_eof' => true, 25 | 'strict_param' => true, 26 | )) 27 | ->setRiskyAllowed(true) 28 | ->setFinder($finder) 29 | ; 30 | 31 | return $config; 32 | -------------------------------------------------------------------------------- /sources/Lib/MarkupValidator/MarkupProviderInterface.php: -------------------------------------------------------------------------------- 1 | setConfiguration($configuration); 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function setConfiguration(array $configuration) 40 | { 41 | $this->configuration = array_merge($this->configuration, $configuration); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Base/TestCase.php: -------------------------------------------------------------------------------- 1 | customSetExpectedException($arguments); 18 | break; 19 | } 20 | } 21 | 22 | private function customSetExpectedException($arguments) 23 | { 24 | $exceptionClass = $arguments[0]; 25 | $this->expectException($exceptionClass); 26 | 27 | $exceptionMessage = $arguments[1]; 28 | $this->expectExceptionMessage($exceptionMessage); 29 | 30 | if (isset($arguments[2]) === true) { 31 | $exceptionCode = $arguments[2]; 32 | $this->expectExceptionCode($exceptionCode); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kolyunya/codeception-markup-validator", 3 | "description": "Markup validator module for Codeception.", 4 | "type": "library", 5 | "license": "LGPL-3.0-or-later", 6 | "minimum-stability": "stable", 7 | "homepage": "https://github.com/Kolyunya/codeception-markup-validator", 8 | "keywords": [ 9 | "acceptance-testing", 10 | "codeception", 11 | "codeception-module", 12 | "html-validator", 13 | "markup-validator", 14 | "w3c-validator" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "Kolyunya", 19 | "email": "oleynikovny@mail.ru", 20 | "homepage": "http://github.com/Kolyunya" 21 | } 22 | ], 23 | "require": { 24 | "php": ">=8.1 <9.0", 25 | "codeception/codeception": ">=2.0 <6.0", 26 | "guzzlehttp/guzzle": "^7.0" 27 | }, 28 | "require-dev": { 29 | "friendsofphp/php-cs-fixer": "^3.0", 30 | "phpstan/phpstan": "^1.9", 31 | "phpunit/phpunit": "^10.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Kolyunya\\Codeception\\": "sources" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Kolyunya\\Codeception\\Tests\\": "tests" 41 | } 42 | }, 43 | "config": { 44 | "sort-packages": true 45 | }, 46 | "scripts": { 47 | "validate-style": "PHP_CS_FIXER_IGNORE_ENV=TRUE vendor/bin/php-cs-fixer fix --dry-run", 48 | "analyze-sources": "vendor/bin/phpstan analyze --configuration=.phpstan-sources.neon", 49 | "analyze-tests": "vendor/bin/phpstan analyze --configuration=.phpstan-tests.neon", 50 | "run-test": "vendor/bin/phpunit" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sources/Lib/MarkupValidator/MarkupValidatorMessageInterface.php: -------------------------------------------------------------------------------- 1 | 34 | docker run --volume $PWD:/sources --workdir /sources --env PHP_CS_FIXER_IGNORE_ENV=TRUE php:${{ matrix.php-version }} 35 | vendor/bin/php-cs-fixer fix --dry-run 36 | - 37 | name: Analyze sources 38 | run: > 39 | docker run --volume $PWD:/sources --workdir /sources php:${{ matrix.php-version }} 40 | vendor/bin/phpstan analyze --configuration=.phpstan-sources.neon 41 | - 42 | name: Analyze tests 43 | run: > 44 | docker run --volume $PWD:/sources --workdir /sources php:${{ matrix.php-version }} 45 | vendor/bin/phpstan analyze --configuration=.phpstan-tests.neon 46 | - 47 | name: Run tests 48 | run: > 49 | docker run --volume $PWD:/sources --workdir /sources php:${{ matrix.php-version }} 50 | vendor/bin/phpunit 51 | -------------------------------------------------------------------------------- /tests/Lib/Base/ComponentTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($classNameActual, $classNameExpected); 34 | } 35 | 36 | public static function dataProviderGetClassName() 37 | { 38 | return array( 39 | array( 40 | 'Kolyunya\Codeception\Lib\Base\Component', 41 | Component::getClassName(), 42 | ), 43 | array( 44 | 'Kolyunya\Codeception\Lib\MarkupValidator\DefaultMarkupProvider', 45 | DefaultMarkupProvider::getClassName(), 46 | ), 47 | array( 48 | 'Kolyunya\Codeception\Lib\MarkupValidator\DefaultMessageFilter', 49 | DefaultMessageFilter::getClassName(), 50 | ), 51 | array( 52 | 'Kolyunya\Codeception\Lib\MarkupValidator\DefaultMessagePrinter', 53 | DefaultMessagePrinter::getClassName(), 54 | ), 55 | array( 56 | 'Kolyunya\Codeception\Lib\MarkupValidator\W3CMarkupValidator', 57 | W3CMarkupValidator::getClassName(), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sources/Lib/MarkupValidator/DefaultMessagePrinter.php: -------------------------------------------------------------------------------- 1 | getMessageStringTemplate(), array( 25 | $message->getType(), 26 | $message->getSummary() ?: self::UNAVAILABLE_DATA_PLACEHOLDER, 27 | $message->getDetails() ?: self::UNAVAILABLE_DATA_PLACEHOLDER, 28 | $message->getFirstLineNumber() ?: self::UNAVAILABLE_DATA_PLACEHOLDER, 29 | $message->getLastLineNumber() ?: self::UNAVAILABLE_DATA_PLACEHOLDER, 30 | $message->getMarkup() ?: self::UNAVAILABLE_DATA_PLACEHOLDER, 31 | )); 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | public function getMessagesString(array $messages) 38 | { 39 | $messagesStrings = array_map(array($this, 'getMessageString'), $messages); 40 | $messagesString = implode("\n", $messagesStrings); 41 | 42 | return $messagesString; 43 | } 44 | 45 | /** 46 | * Returns message string representation template. 47 | * 48 | * @return string Message string representation template. 49 | */ 50 | protected function getMessageStringTemplate() 51 | { 52 | return 53 | <<moduleContainer = $moduleContainer; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | public function getMarkup() 37 | { 38 | try { 39 | return $this->getMarkupFromPhpBrowser(); 40 | } catch (Exception $exception) { 41 | // Wasn't able to get markup from the `PhpBrowser` module. 42 | } 43 | 44 | try { 45 | return $this->getMarkupFromWebDriver(); 46 | } catch (Exception $exception) { 47 | // Wasn't able to get markup from the `WebDriver` module. 48 | } 49 | 50 | throw new Exception('Unable to obtain current page markup.'); 51 | } 52 | 53 | /** 54 | * Returns current page markup form the `PhpBrowser` module. 55 | * 56 | * @return string Current page markup. 57 | */ 58 | private function getMarkupFromPhpBrowser() 59 | { 60 | /* @var $phpBrowser PhpBrowser */ 61 | $phpBrowser = $this->getModule('PhpBrowser'); 62 | $markup = $phpBrowser->_getResponseContent(); 63 | 64 | return $markup; 65 | } 66 | 67 | /** 68 | * Returns current page markup form the `WebDriver` module. 69 | * 70 | * @return string Current page markup. 71 | */ 72 | private function getMarkupFromWebDriver() 73 | { 74 | /* @var $webDriver WebDriver */ 75 | $webDriver = $this->getModule('WebDriver'); 76 | $markup = $webDriver->webDriver->getPageSource(); 77 | 78 | return $markup; 79 | } 80 | 81 | /** 82 | * Returns a module instance by its name. 83 | * 84 | * @param string $name Module name. 85 | * @return object Module instance. 86 | */ 87 | private function getModule($name) 88 | { 89 | if (!$this->moduleContainer->hasModule($name)) { 90 | throw new Exception(sprintf('«%s» module is not available.', $name)); 91 | } 92 | 93 | $module = $this->moduleContainer->getModule($name); 94 | 95 | return $module; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /sources/Lib/MarkupValidator/W3CMarkupValidatorMessage.php: -------------------------------------------------------------------------------- 1 | initializeType($data); 24 | $this->initializeSummary($data); 25 | $this->initializeFirstLineNumber($data); 26 | $this->initializeLastLineNumber($data); 27 | $this->initializeMarkup($data); 28 | } 29 | 30 | /** 31 | * Initializes message type. 32 | * 33 | * @param array $data Message data. 34 | */ 35 | private function initializeType(array $data) 36 | { 37 | if (isset($data['type']) === false) { 38 | return; 39 | } 40 | 41 | if ($data['type'] === 'error') { 42 | $this->type = self::TYPE_ERROR; 43 | } elseif ($data['type'] === 'info') { 44 | if (isset($data['subType']) === true && 45 | $data['subType'] === 'warning' 46 | ) { 47 | $this->type = self::TYPE_WARNING; 48 | } else { 49 | $this->type = self::TYPE_INFO; 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * Initializes message summary. 56 | * 57 | * @param array $data Message data. 58 | */ 59 | private function initializeSummary(array $data) 60 | { 61 | if (isset($data['message']) === true) { 62 | $this->setSummary($data['message']); 63 | } 64 | } 65 | 66 | /** 67 | * Initializes first line number. 68 | * 69 | * @param array $data Message data. 70 | */ 71 | private function initializeFirstLineNumber(array $data) 72 | { 73 | if (isset($data['firstLine']) === true) { 74 | $this->setFirstLineNumber($data['firstLine']); 75 | } 76 | } 77 | 78 | /** 79 | * Initializes last line number. 80 | * 81 | * @param array $data Message data. 82 | */ 83 | private function initializeLastLineNumber(array $data) 84 | { 85 | if (isset($data['lastLine']) === true) { 86 | $this->setLastLineNumber($data['lastLine']); 87 | } 88 | } 89 | 90 | /** 91 | * Initializes message markup. 92 | * 93 | * @param array $data Message data. 94 | */ 95 | private function initializeMarkup(array $data) 96 | { 97 | if (isset($data['extract']) === true) { 98 | $this->setMarkup($data['extract']); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /sources/Lib/MarkupValidator/W3CMarkupValidator.php: -------------------------------------------------------------------------------- 1 | 'https://validator.w3.org/', 24 | self::ENDPOINT_CONFIG_KEY => '/nu/', 25 | ); 26 | 27 | /** 28 | * HTTP client used to communicate with the W3C Markup Validation Service. 29 | * 30 | * @var Client 31 | */ 32 | private $httpClient; 33 | 34 | /** 35 | * Parameters of a HTTP request to the W3C Markup Validation Service. 36 | * 37 | * @var array 38 | */ 39 | private $httpRequestParameters; 40 | 41 | /** 42 | * {@inheritDoc} 43 | */ 44 | public function __construct(array $configuration = array()) 45 | { 46 | parent::__construct($configuration); 47 | 48 | $this->initializeHttpClient(); 49 | $this->initializeHttpRequestParameters(); 50 | } 51 | 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | public function validate($markup) 56 | { 57 | $validationData = $this->getValidationData($markup); 58 | $validationMessages = $this->getValidationMessages($validationData); 59 | 60 | return $validationMessages; 61 | } 62 | 63 | /** 64 | * Initializes HTTP client used to communicate with the W3C Markup Validation Service. 65 | */ 66 | private function initializeHttpClient() 67 | { 68 | $this->httpClient = new Client(array( 69 | 'base_uri' => $this->configuration[self::BASE_URI_CONFIG_KEY], 70 | )); 71 | } 72 | 73 | /** 74 | * Initializes parameters of a HTTP request to the W3C Markup Validation Service. 75 | */ 76 | private function initializeHttpRequestParameters() 77 | { 78 | $this->httpRequestParameters = array( 79 | 'headers' => array( 80 | 'Content-Type' => 'text/html; charset=UTF-8;', 81 | ), 82 | 'query' => array( 83 | 'out' => 'json', 84 | ), 85 | ); 86 | } 87 | 88 | /** 89 | * Sends a validation request to a W3C Markup Validation Service 90 | * and returns decoded validation data. 91 | * 92 | * @param string $markup Markup to get validation data for. 93 | * @return array Validation data for provided markup. 94 | */ 95 | private function getValidationData($markup) 96 | { 97 | $this->httpRequestParameters['body'] = $markup; 98 | 99 | $reponse = $this->httpClient->post( 100 | $this->configuration[self::ENDPOINT_CONFIG_KEY], 101 | $this->httpRequestParameters 102 | ); 103 | $responseData = $reponse->getBody()->getContents(); 104 | $validationData = json_decode($responseData, true); 105 | if ($validationData === null) { 106 | throw new Exception('Unable to parse W3C Markup Validation Service response.'); 107 | } 108 | 109 | return $validationData; 110 | } 111 | 112 | /** 113 | * Parses validation data and returns validation messages. 114 | * 115 | * @param array $validationData Validation data. 116 | * @return MarkupValidatorMessageInterface[] Validation messages. 117 | */ 118 | private function getValidationMessages(array $validationData) 119 | { 120 | $messages = array(); 121 | $messagesData = $validationData['messages']; 122 | foreach ($messagesData as $messageData) { 123 | $message = new W3CMarkupValidatorMessage($messageData); 124 | $messages[] = $message; 125 | } 126 | 127 | return $messages; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/Lib/MarkupValidator/DefaultMarkupProviderTest.php: -------------------------------------------------------------------------------- 1 | moduleContainer = $this 28 | ->getMockBuilder('Codeception\Lib\ModuleContainer') 29 | ->disableOriginalConstructor() 30 | ->getMock() 31 | ; 32 | 33 | $this->provider = new DefaultMarkupProvider($this->moduleContainer); 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function tearDown(): void 40 | { 41 | } 42 | 43 | public function testWithNoPhpBrowserNoWebDriver() 44 | { 45 | $this->setExpectedException('Exception', 'Unable to obtain current page markup.'); 46 | $this->provider->getMarkup(); 47 | } 48 | 49 | public function testWithPhpBrowser() 50 | { 51 | $expectedMarkup = 52 | << 54 | 55 | 56 | 57 | A valid page. 58 | 59 | 60 | 61 | HTML 62 | ; 63 | 64 | $phpBrowser = $this 65 | ->getMockBuilder('Codeception\Module') 66 | ->disableOriginalConstructor() 67 | ->addMethods(array( 68 | '_getResponseContent', 69 | )) 70 | ->getMock() 71 | ; 72 | $phpBrowser 73 | ->method('_getResponseContent') 74 | ->will($this->returnValue($expectedMarkup)) 75 | ; 76 | 77 | $this->moduleContainer 78 | ->method('hasModule') 79 | ->will($this->returnValueMap(array( 80 | array('PhpBrowser', true) 81 | ))) 82 | ; 83 | $this->moduleContainer 84 | ->method('getModule') 85 | ->will($this->returnValueMap(array( 86 | array('PhpBrowser', $phpBrowser) 87 | ))) 88 | ; 89 | 90 | $actualMarkup = $this->provider->getMarkup(); 91 | $this->assertEquals($expectedMarkup, $actualMarkup); 92 | } 93 | 94 | public function testWithWebDriver() 95 | { 96 | $expectedMarkup = 97 | << 99 | 100 | 101 | 102 | A valid page. 103 | 104 | 105 | 106 | HTML 107 | ; 108 | 109 | $remoteWebDriver = $this 110 | ->getMockBuilder('Codeception\Module') 111 | ->disableOriginalConstructor() 112 | ->addMethods(array( 113 | 'getPageSource', 114 | )) 115 | ->getMock() 116 | ; 117 | $remoteWebDriver 118 | ->method('getPageSource') 119 | ->will($this->returnValue($expectedMarkup)) 120 | ; 121 | 122 | $webDriver = $this 123 | ->getMockBuilder('Codeception\Module') 124 | ->disableOriginalConstructor() 125 | ->getMock() 126 | ; 127 | $webDriver->webDriver = $remoteWebDriver; 128 | 129 | $this->moduleContainer 130 | ->method('hasModule') 131 | ->will($this->returnValueMap(array( 132 | array('PhpBrowser', false), 133 | array('WebDriver', true) 134 | ))) 135 | ; 136 | $this->moduleContainer 137 | ->method('getModule') 138 | ->will($this->returnValueMap(array( 139 | array('WebDriver', $webDriver) 140 | ))) 141 | ; 142 | 143 | $actualMarkup = $this->provider->getMarkup(); 144 | $this->assertEquals($expectedMarkup, $actualMarkup); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /sources/Lib/MarkupValidator/DefaultMessageFilter.php: -------------------------------------------------------------------------------- 1 | 0, 28 | self::IGNORE_WARNINGS_CONFIG_KEY => true, 29 | self::IGNORED_ERRORS_CONFIG_KEY => array(), 30 | ); 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | public function filterMessages(array $messages) 36 | { 37 | $filteredMessages = array(); 38 | 39 | foreach ($messages as $message) { 40 | /* @var $message MarkupValidatorMessageInterface */ 41 | $messageType = $message->getType(); 42 | 43 | if ($messageType === MarkupValidatorMessageInterface::TYPE_UNDEFINED || 44 | $messageType === MarkupValidatorMessageInterface::TYPE_INFO 45 | ) { 46 | continue; 47 | } 48 | 49 | if ($messageType === MarkupValidatorMessageInterface::TYPE_WARNING && 50 | $this->ignoreWarnings() === true 51 | ) { 52 | continue; 53 | } 54 | 55 | if ($this->ignoreError($message->getSummary()) === true) { 56 | continue; 57 | } 58 | 59 | $filteredMessages[] = $message; 60 | } 61 | 62 | if ($this->belowErrorCountThreshold($filteredMessages) === true) { 63 | // Error count threshold was not reached. 64 | return array(); 65 | } 66 | 67 | return $filteredMessages; 68 | } 69 | 70 | /** 71 | * Returns a boolean indicating whether messages count 72 | * is below the threshold or not. 73 | * 74 | * @param array $messages Messages to report about. 75 | * 76 | * @return boolean Whether messages count is below the threshold or not. 77 | */ 78 | private function belowErrorCountThreshold(array $messages) 79 | { 80 | if (is_int($this->configuration[self::ERROR_COUNT_THRESHOLD_KEY]) === false) { 81 | throw new Exception(sprintf('Invalid «%s» config key.', self::ERROR_COUNT_THRESHOLD_KEY)); 82 | } 83 | 84 | $threshold = $this->configuration[self::ERROR_COUNT_THRESHOLD_KEY]; 85 | $belowThreshold = count($messages) <= $threshold; 86 | 87 | return $belowThreshold; 88 | } 89 | 90 | /** 91 | * Returns a boolean indicating whether the filter ignores warnings or not. 92 | * 93 | * @return bool Whether the filter ignores warnings or not. 94 | */ 95 | private function ignoreWarnings() 96 | { 97 | if (is_bool($this->configuration[self::IGNORE_WARNINGS_CONFIG_KEY]) === false) { 98 | throw new Exception(sprintf('Invalid «%s» config key.', self::IGNORE_WARNINGS_CONFIG_KEY)); 99 | } 100 | 101 | /* @var $ignoreWarnings bool */ 102 | $ignoreWarnings = $this->configuration[self::IGNORE_WARNINGS_CONFIG_KEY]; 103 | 104 | return $ignoreWarnings; 105 | } 106 | 107 | /** 108 | * Returns a boolean indicating whether an error is ignored or not. 109 | * 110 | * @param string|null $summary Error summary. 111 | * @return boolean Whether an error is ignored or not. 112 | */ 113 | private function ignoreError($summary) 114 | { 115 | if (is_array($this->configuration[self::IGNORED_ERRORS_CONFIG_KEY]) === false) { 116 | throw new Exception(sprintf('Invalid «%s» config key.', self::IGNORED_ERRORS_CONFIG_KEY)); 117 | } 118 | 119 | $ignoreError = false; 120 | 121 | if ($summary === null) { 122 | return $ignoreError; 123 | } 124 | 125 | $ignoredErrors = $this->configuration[self::IGNORED_ERRORS_CONFIG_KEY]; 126 | foreach ($ignoredErrors as $ignoredError) { 127 | $erorIsIgnored = preg_match($ignoredError, $summary) === 1; 128 | if ($erorIsIgnored) { 129 | $ignoreError = true; 130 | break; 131 | } 132 | } 133 | 134 | return $ignoreError; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/Lib/MarkupValidator/W3CMarkupValidatorTest.php: -------------------------------------------------------------------------------- 1 | validator = new W3CMarkupValidator(); 22 | } 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | public function tearDown(): void 28 | { 29 | } 30 | 31 | /** 32 | * @dataProvider dataProviderValidateMarkup 33 | */ 34 | public function testValidateMarkup($markup, $messagesData) 35 | { 36 | $messages = $this->validator->validate($markup); 37 | 38 | $this->assertEquals(count($messagesData), count($messages)); 39 | 40 | foreach ($messagesData as $messageIndex => $messageData) { 41 | $message = $messages[$messageIndex]; 42 | 43 | $this->assertEquals($message->getType(), $messageData['type']); 44 | $this->assertEquals($message->getSummary(), $messageData['summary']); 45 | $this->assertEquals($message->getDetails(), $messageData['details']); 46 | $this->assertEquals($message->getFirstLineNumber(), $messageData['firstLineNumber']); 47 | $this->assertEquals($message->getLastLineNumber(), $messageData['lastLineNumber']); 48 | $this->assertStringContainsString($messageData['markup'], $message->getMarkup()); 49 | } 50 | } 51 | 52 | public function testInvalidValidationServiceResponse() 53 | { 54 | $this->setExpectedException('Exception', 'Unable to parse W3C Markup Validation Service response.'); 55 | 56 | $this->validator->setConfiguration(array( 57 | 'baseUri' => 'https://validator.w3.org/', 58 | 'endpoint' => '/', 59 | )); 60 | $this->validator->validate(''); 61 | } 62 | 63 | public static function dataProviderValidateMarkup() 64 | { 65 | return array( 66 | array( 67 | << 69 | 70 | 71 | 72 | A valid page. 73 | 74 | 75 | 76 | HTML 77 | , 78 | array( 79 | ), 80 | ), 81 | array( 82 | << 84 | 85 | 86 | 87 | 88 | HTML 89 | , 90 | array( 91 | array( 92 | 'type' => MarkupValidatorMessageInterface::TYPE_ERROR, 93 | 'summary' => 'Element “head” is missing a required instance of child element “title”.', 94 | 'details' => null, 95 | 'markup' => '', 96 | 'firstLineNumber' => null, 97 | 'lastLineNumber' => 4, 98 | ), 99 | ), 100 | ), 101 | array( 102 | << 104 | 105 | 106 | 107 | 108 |
109 | 111 |
112 | 113 | 114 | HTML 115 | , 116 | array( 117 | array( 118 | 'type' => MarkupValidatorMessageInterface::TYPE_ERROR, 119 | 'summary' => 'Element “head” is missing a required instance of child element “title”.', 120 | 'details' => null, 121 | 'markup' => '', 122 | 'firstLineNumber' => null, 123 | 'lastLineNumber' => 4, 124 | ), 125 | array( 126 | 'type' => MarkupValidatorMessageInterface::TYPE_WARNING, 127 | 'summary' => 'The “button” role is unnecessary for element “button”.', 128 | 'details' => null, 129 | 'markup' => ' 240 | 241 | 242 | 243 | HTML 244 | , 245 | false, 246 | ), 247 | array( 248 | << 250 | 251 | 252 | 253 | A page with a warning. 254 | 255 | 256 | 257 |
258 | 260 |
261 | 262 | 263 | HTML 264 | , 265 | false, 266 | ), 267 | ); 268 | } 269 | 270 | public static function dataProviderOverrideFilterConfigurationWarnings() 271 | { 272 | return array( 273 | array( 274 | << 276 | 277 | 278 | 279 | A page with a warning. 280 | 281 | 282 | 283 |
284 | 286 |
287 | 288 | 289 | HTML 290 | , 291 | ), 292 | ); 293 | } 294 | 295 | public static function dataProviderOverrideFilterConfigurationErrors() 296 | { 297 | return array( 298 | array( 299 | << 301 | 302 | 303 | 304 | 305 | HTML 306 | , 307 | array( 308 | '/Element “head” is missing a required instance of child element “title”./', 309 | ), 310 | ), 311 | ); 312 | } 313 | 314 | private function mockMarkup($markup) 315 | { 316 | $phpBrowser = $this 317 | ->getMockBuilder('Codeception\Module') 318 | ->disableOriginalConstructor() 319 | ->addMethods(array( 320 | '_getResponseContent', 321 | )) 322 | ->getMock() 323 | ; 324 | $phpBrowser 325 | ->method('_getResponseContent') 326 | ->will($this->returnValue($markup)) 327 | ; 328 | 329 | $this->moduleContainer 330 | ->method('hasModule') 331 | ->will($this->returnValueMap(array( 332 | array('PhpBrowser', true), 333 | ))) 334 | ; 335 | $this->moduleContainer 336 | ->method('getModule') 337 | ->will($this->returnValueMap(array( 338 | array('PhpBrowser', $phpBrowser), 339 | ))) 340 | ; 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /tests/Lib/MarkupValidator/DefaultMessageFilterTest.php: -------------------------------------------------------------------------------- 1 | filter = new DefaultMessageFilter(); 24 | } 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public function tearDown(): void 30 | { 31 | } 32 | 33 | /** 34 | * @dataProvider dataProviderFilterMessages 35 | */ 36 | public function testFilterMessages($sourceMessages, $filteredMessagesExpected) 37 | { 38 | $this->filter->setConfiguration(array( 39 | 'ignoreWarnings' => false, 40 | 'ignoredErrors' => array(), 41 | )); 42 | 43 | $filteredMessagesActual = $this->filter->filterMessages($sourceMessages); 44 | 45 | $this->assertEquals(count($filteredMessagesExpected), count($filteredMessagesActual)); 46 | $this->assertArraySubset($filteredMessagesExpected, $filteredMessagesActual); 47 | } 48 | 49 | /** 50 | * @dataProvider dataProviderErrorCountThreshold 51 | */ 52 | public function testerrorCountThreshold($messages, $threshold, $filteredMessagesExpected) 53 | { 54 | $this->filter->setConfiguration(array( 55 | 'errorCountThreshold' => $threshold, 56 | )); 57 | 58 | $filteredMessagesActual = $this->filter->filterMessages($messages); 59 | 60 | $this->assertEquals(count($filteredMessagesExpected), count($filteredMessagesActual)); 61 | $this->assertArraySubset($filteredMessagesExpected, $filteredMessagesActual); 62 | } 63 | 64 | /** 65 | * @dataProvider dataProviderIgnoreWarnings 66 | */ 67 | public function testIgnoreWarnings($messages, $filteredMessagesExpected) 68 | { 69 | $this->filter->setConfiguration(array( 70 | 'ignoreWarnings' => true, 71 | )); 72 | 73 | $filteredMessagesActual = $this->filter->filterMessages($messages); 74 | 75 | $this->assertEquals(count($filteredMessagesExpected), count($filteredMessagesActual)); 76 | $this->assertArraySubset($filteredMessagesExpected, $filteredMessagesActual); 77 | } 78 | 79 | /** 80 | * @dataProvider dataProviderIgnoredErrors 81 | */ 82 | public function testIgnoredErrors($messages, $ignoredErrors, $filteredMessagesExpected) 83 | { 84 | $this->filter->setConfiguration(array( 85 | 'ignoredErrors' => $ignoredErrors, 86 | )); 87 | 88 | $filteredMessagesActual = $this->filter->filterMessages($messages); 89 | 90 | $this->assertEquals(count($filteredMessagesExpected), count($filteredMessagesActual)); 91 | $this->assertArraySubset($filteredMessagesExpected, $filteredMessagesActual); 92 | } 93 | 94 | public function testInvaliderrorCountThresholdConfig() 95 | { 96 | $this->setExpectedException('Exception', 'Invalid «errorCountThreshold» config key.'); 97 | 98 | $warning = new MarkupValidatorMessage(); 99 | $warning->setType(MarkupValidatorMessageInterface::TYPE_WARNING); 100 | 101 | $this->filter->setConfiguration(array( 102 | 'errorCountThreshold' => true, 103 | )); 104 | $this->filter->filterMessages(array($warning)); 105 | } 106 | 107 | public function testInvalidIgnoreWarningsConfig() 108 | { 109 | $this->setExpectedException('Exception', 'Invalid «ignoreWarnings» config key.'); 110 | 111 | $warning = new MarkupValidatorMessage(); 112 | $warning->setType(MarkupValidatorMessageInterface::TYPE_WARNING); 113 | 114 | $this->filter->setConfiguration(array( 115 | 'ignoreWarnings' => array( 116 | 'foo' => false, 117 | 'bar' => true, 118 | ), 119 | )); 120 | $this->filter->filterMessages(array($warning)); 121 | } 122 | 123 | public function testInvalidIgnoreErrorsConfig() 124 | { 125 | $this->setExpectedException('Exception', 'Invalid «ignoredErrors» config key.'); 126 | 127 | $error = new MarkupValidatorMessage(); 128 | $error->setType(MarkupValidatorMessageInterface::TYPE_ERROR); 129 | 130 | $this->filter->setConfiguration(array( 131 | 'ignoredErrors' => false, 132 | )); 133 | $this->filter->filterMessages(array($error)); 134 | } 135 | 136 | public static function dataProviderErrorCountThreshold() 137 | { 138 | return array( 139 | array( 140 | array( 141 | ), 142 | 0, 143 | array( 144 | 145 | ), 146 | ), 147 | array( 148 | array( 149 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 150 | ), 151 | 1, 152 | array( 153 | 154 | ), 155 | ), 156 | array( 157 | array( 158 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 159 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 160 | ), 161 | 2, 162 | array( 163 | 164 | ), 165 | ), 166 | array( 167 | array( 168 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 169 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 170 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 171 | ), 172 | 5, 173 | array( 174 | 175 | ), 176 | ), 177 | array( 178 | array( 179 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 180 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 181 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 182 | ), 183 | -1, 184 | array( 185 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 186 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 187 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 188 | ), 189 | ), 190 | array( 191 | array( 192 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 193 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 194 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 195 | ), 196 | 2, 197 | array( 198 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 199 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 200 | new MarkupValidatorMessage(MarkupValidatorMessageInterface::TYPE_ERROR), 201 | ), 202 | ), 203 | ); 204 | } 205 | 206 | public static function dataProviderFilterMessages() 207 | { 208 | return array( 209 | array( 210 | array( 211 | (new MarkupValidatorMessage()) 212 | ->setType(MarkupValidatorMessageInterface::TYPE_UNDEFINED) 213 | ), 214 | array( 215 | 216 | ), 217 | ), 218 | array( 219 | array( 220 | (new MarkupValidatorMessage()) 221 | ->setType(MarkupValidatorMessageInterface::TYPE_INFO) 222 | ), 223 | array( 224 | 225 | ), 226 | ), 227 | array( 228 | array( 229 | (new MarkupValidatorMessage()) 230 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 231 | ->setSummary('Warning text.') 232 | ->setMarkup('

') 233 | , 234 | ), 235 | array( 236 | (new MarkupValidatorMessage()) 237 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 238 | ->setSummary('Warning text.') 239 | ->setMarkup('

') 240 | , 241 | ), 242 | ), 243 | array( 244 | array( 245 | (new MarkupValidatorMessage()) 246 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 247 | ->setSummary('Error text.') 248 | ->setMarkup('') 249 | , 250 | ), 251 | array( 252 | (new MarkupValidatorMessage()) 253 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 254 | ->setSummary('Error text.') 255 | ->setMarkup('') 256 | , 257 | ), 258 | ), 259 | ); 260 | } 261 | 262 | public static function dataProviderIgnoreWarnings() 263 | { 264 | return array( 265 | array( 266 | array( 267 | (new MarkupValidatorMessage()) 268 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 269 | , 270 | (new MarkupValidatorMessage()) 271 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 272 | , 273 | ), 274 | array( 275 | (new MarkupValidatorMessage()) 276 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 277 | , 278 | ), 279 | ), 280 | array( 281 | array( 282 | (new MarkupValidatorMessage()) 283 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 284 | , 285 | (new MarkupValidatorMessage()) 286 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 287 | , 288 | (new MarkupValidatorMessage()) 289 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 290 | , 291 | ), 292 | array( 293 | (new MarkupValidatorMessage()) 294 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 295 | , 296 | (new MarkupValidatorMessage()) 297 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 298 | , 299 | ), 300 | ), 301 | array( 302 | array( 303 | (new MarkupValidatorMessage()) 304 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 305 | , 306 | (new MarkupValidatorMessage()) 307 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 308 | , 309 | (new MarkupValidatorMessage()) 310 | ->setType(MarkupValidatorMessageInterface::TYPE_WARNING) 311 | , 312 | ), 313 | array( 314 | 315 | ), 316 | ), 317 | ); 318 | } 319 | 320 | public static function dataProviderIgnoredErrors() 321 | { 322 | return array( 323 | array( 324 | array( 325 | (new MarkupValidatorMessage()) 326 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 327 | ->setSummary('Some error message.') 328 | , 329 | (new MarkupValidatorMessage()) 330 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 331 | ->setSummary('Some cryptic error message.') 332 | , 333 | ), 334 | array( 335 | '/some error/i', 336 | '/cryptic error/', 337 | '/other error/', 338 | ), 339 | array( 340 | 341 | ), 342 | ), 343 | array( 344 | array( 345 | (new MarkupValidatorMessage()) 346 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 347 | ->setSummary('Some cryptic error message.') 348 | , 349 | ), 350 | array( 351 | '/some error/', 352 | '/other error/', 353 | ), 354 | array( 355 | (new MarkupValidatorMessage()) 356 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 357 | ->setSummary('Some cryptic error message.') 358 | , 359 | ), 360 | ), 361 | array( 362 | array( 363 | (new MarkupValidatorMessage()) 364 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 365 | ->setSummary('Some cryptic error message.') 366 | , 367 | ), 368 | array( 369 | '/cryptic error/', 370 | ), 371 | array( 372 | 373 | ), 374 | ), 375 | array( 376 | array( 377 | (new MarkupValidatorMessage()) 378 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 379 | ->setSummary('Case insensitive error message.') 380 | , 381 | ), 382 | array( 383 | '/case insensitive error message./i', 384 | ), 385 | array( 386 | 387 | ), 388 | ), 389 | array( 390 | array( 391 | (new MarkupValidatorMessage()) 392 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 393 | ->setSummary('Текст ошибки в UTF-8.') 394 | , 395 | ), 396 | array( 397 | '/Текст ошибки в UTF-8./u', 398 | ), 399 | array( 400 | 401 | ), 402 | ), 403 | array( 404 | array( 405 | (new MarkupValidatorMessage()) 406 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 407 | , 408 | ), 409 | array( 410 | '/error/', 411 | ), 412 | array( 413 | (new MarkupValidatorMessage()) 414 | ->setType(MarkupValidatorMessageInterface::TYPE_ERROR) 415 | , 416 | ), 417 | ), 418 | ); 419 | } 420 | } 421 | --------------------------------------------------------------------------------