diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 98%
rename from CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
index 1c0658b..c581be8 100644
--- a/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -44,7 +44,7 @@ This project uses [the PSR-2 coding standards](http://www.php-fig.org/psr/psr-2/
[PHPUnit](https://phpunit.de/) is included as a development dependency, and should be run regularly. When submitting changes, please be sure to add or update unit tests accordingly. You may run unit tests at any time by running:
```bash
-$ ./vendor/bin/phpunit
+$ composer test
```
#### Code coverage
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
new file mode 100644
index 0000000..6eee129
--- /dev/null
+++ b/.github/workflows/code-coverage.yml
@@ -0,0 +1,35 @@
+name: Code Coverage
+
+on:
+ pull_request:
+ push:
+ branches:
+ - develop
+ - main
+
+jobs:
+ coverage:
+ name: Report code coverage
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ coverage: xdebug
+
+ - name: Install Composer dependencies
+ uses: ramsey/composer-install@v2
+
+ - name: Run test suite
+ run: vendor/bin/simple-phpunit --coverage-text --coverage-clover=tests/coverage
+
+ - name: Publish to Coveralls
+ uses: coverallsapp/github-action@v2
+ with:
+ files: tests/coverage
+ format: clover
+ fail-on-error: false
diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
new file mode 100644
index 0000000..68d77bc
--- /dev/null
+++ b/.github/workflows/coding-standards.yml
@@ -0,0 +1,23 @@
+name: Coding Standards
+
+on: [pull_request]
+
+jobs:
+ phpcs:
+ name: PHP_CodeSniffer
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ coverage: none
+
+ - name: Install Composer dependencies
+ uses: ramsey/composer-install@v2
+
+ - name: Run test suite
+ run: composer coding-standards
diff --git a/.github/workflows/static-code-analysis.yml b/.github/workflows/static-code-analysis.yml
new file mode 100644
index 0000000..745f15f
--- /dev/null
+++ b/.github/workflows/static-code-analysis.yml
@@ -0,0 +1,28 @@
+name: Static Code Analysis
+
+on: [pull_request]
+
+jobs:
+ phpcs:
+ name: PHPStan
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ coverage: none
+
+ - name: Install Composer dependencies
+ uses: ramsey/composer-install@v2
+
+ # PHPUnit Bridge won't install a version of PHPUnit by default, but this will trick
+ # it into doing so.
+ - name: Install PHPUnit
+ run: composer test -- --version
+
+ - name: Run PHPStan
+ run: composer static-analysis
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 84df900..0ec9256 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
+ php-version: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -19,6 +19,9 @@ jobs:
php-version: ${{ matrix.php-version }}
coverage: none
+ - name: Remove PHPStan as a dependency
+ run: composer remove --dev phpstan/phpstan
+
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
diff --git a/.gitignore b/.gitignore
index bb4f4cb..415274e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,11 @@
-tests/coverage
-vendor
+*.DS_Store
.phpunit.result.cache
.vscode
+phpcs.xml
+phpstan.neon
+phpunit.xml
+tests/coverage
+vendor
# The composer.lock file is not needed, as this is a library whose dependencies
# will depend on the version of PHP being used.
diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
new file mode 100644
index 0000000..df2db46
--- /dev/null
+++ b/.phpcs.xml.dist
@@ -0,0 +1,24 @@
+
+
+ Coding standards for PHPUnit Markup Assertions
+
+
+
+
+
+
+ .
+ */vendor/*
+
+
+
+
+
+
+ tests/*
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 861f893..8915add 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
# PHPUnit Markup Assertions

-[](https://github.com/stevegrunwell/phpunit-markup-assertions/releases)
+[](https://coveralls.io/github/stevegrunwell/phpunit-markup-assertions?branch=develop)
+[](https://github.com/stevegrunwell/phpunit-markup-assertions/releases)
This library introduces the `MarkupAssertionsTrait` trait for use in [PHPUnit](https://phpunit.de) tests.
diff --git a/composer.json b/composer.json
index a757aca..f3a4abf 100644
--- a/composer.json
+++ b/composer.json
@@ -15,11 +15,16 @@
"source": "https://github.com/stevegrunwell/phpunit-markup-assertions/"
},
"require": {
- "php": "^5.6 || ^7.0 || ^8.0",
- "laminas/laminas-dom": "~2.7.2 || ^2.8"
+ "php": "^7.1 || ^8.0",
+ "symfony/css-selector": "^4.4|^5.4|^6.0|^7.0",
+ "symfony/dom-crawler": "^4.4|^5.4|^6.0|^7.0"
},
"require-dev": {
- "symfony/phpunit-bridge": "^5.2 || ^6.2"
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "phpcompatibility/php-compatibility": "^9.3",
+ "phpstan/phpstan": "^2.1",
+ "squizlabs/php_codesniffer": "^3.7",
+ "symfony/phpunit-bridge": "^5.2 || ^6.2 || ^7.0"
},
"autoload": {
"psr-4": {
@@ -32,19 +37,38 @@
}
},
"scripts": {
+ "coding-standards": [
+ "phpcs"
+ ],
+ "static-analysis": [
+ "phpstan analyse"
+ ],
"test": [
"simple-phpunit --testdox"
],
"test-coverage": [
- "phpdbg -qrr -d memory_limit=-1 ./vendor/bin/simple-phpunit --coverage-html=tests/coverage --colors=always"
+ "XDEBUG_MODE=coverage ./vendor/bin/simple-phpunit --coverage-html=tests/coverage --colors=always"
]
},
"scripts-descriptions": {
+ "coding-standards": "Check coding standards.",
+ "static-analysis": "Run static code analysis",
"test": "Run all test suites.",
"test-coverage": "Generate code coverage reports in tests/coverage."
},
"config": {
"preferred-install": "dist",
- "sort-packages": true
+ "sort-packages": true,
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ },
+ "archive": {
+ "exclude": [
+ "_config.yml",
+ ".*",
+ "phpunit.*",
+ "tests"
+ ]
}
}
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..3cbe92e
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,15 @@
+parameters:
+ level: 10
+
+ # Code to be analyzed
+ paths:
+ - src
+ - tests
+
+ # Don't worry about the coverage reports
+ excludePaths:
+ - tests/coverage (?)
+
+ # PHPUnit Bridge puts the PHPUnit source in an unconventional location
+ scanDirectories:
+ - vendor/bin/.phpunit/phpunit
diff --git a/src/MarkupAssertionsTrait.php b/src/MarkupAssertionsTrait.php
index 3e5cf1f..ea94aea 100644
--- a/src/MarkupAssertionsTrait.php
+++ b/src/MarkupAssertionsTrait.php
@@ -1,4 +1,5 @@
$attributes An array of HTML attributes that should be found
+ * on the element.
+ * @param string $markup The output that should contain an element with the
+ * provided $attributes.
+ * @param string $message A message to display if the assertion fails.
*
* @return void
*/
@@ -94,10 +96,11 @@ public function assertHasElementWithAttributes($attributes = [], $markup = '', $
*
* @since 1.0.0
*
- * @param array $attributes An array of HTML attributes that should be found on the element.
- * @param string $markup The output that should not contain an element with the
- * provided $attributes.
- * @param string $message A message to display if the assertion fails.
+ * @param array $attributes An array of HTML attributes that should be found
+ * on the element.
+ * @param string $markup The output that should not contain an element with
+ * the provided $attributes.
+ * @param string $message A message to display if the assertion fails.
*
* @return void
*/
@@ -124,11 +127,7 @@ public function assertNotHasElementWithAttributes($attributes = [], $markup = ''
*/
public function assertElementContains($contents, $selector = '', $markup = '', $message = '')
{
- $method = method_exists($this, 'assertStringContainsString')
- ? 'assertStringContainsString'
- : 'assertContains';
-
- $this->$method(
+ $this->assertStringContainsString(
$contents,
$this->getInnerHtmlOfMatchedElements($markup, $selector),
$message
@@ -149,11 +148,7 @@ public function assertElementContains($contents, $selector = '', $markup = '', $
*/
public function assertElementNotContains($contents, $selector = '', $markup = '', $message = '')
{
- $method = method_exists($this, 'assertStringNotContainsString')
- ? 'assertStringNotContainsString'
- : 'assertNotContains';
-
- $this->$method(
+ $this->assertStringNotContainsString(
$contents,
$this->getInnerHtmlOfMatchedElements($markup, $selector),
$message
@@ -174,9 +169,10 @@ public function assertElementNotContains($contents, $selector = '', $markup = ''
*/
public function assertElementRegExp($regexp, $selector = '', $markup = '', $message = '')
{
+ // @phpstan-ignore function.alreadyNarrowedType (Introduced in PHPUnit 9.x, PHP 7.3+)
$method = method_exists($this, 'assertMatchesRegularExpression')
? 'assertMatchesRegularExpression'
- : 'assertRegExp';
+ : 'assertRegExp'; // @codeCoverageIgnore
$this->$method(
$regexp,
@@ -199,9 +195,10 @@ public function assertElementRegExp($regexp, $selector = '', $markup = '', $mess
*/
public function assertElementNotRegExp($regexp, $selector = '', $markup = '', $message = '')
{
+ // @phpstan-ignore function.alreadyNarrowedType (Introduced in PHPUnit 9.x, PHP 7.3+)
$method = method_exists($this, 'assertDoesNotMatchRegularExpression')
? 'assertDoesNotMatchRegularExpression'
- : 'assertNotRegExp';
+ : 'assertNotRegExp'; // @codeCoverageIgnore
$this->$method(
$regexp,
@@ -218,15 +215,13 @@ public function assertElementNotRegExp($regexp, $selector = '', $markup = '', $m
* @param string $markup The HTML for the DOMDocument.
* @param string $query The DOM selector query.
*
- * @return \Laminas\Dom\Document\NodeList
+ * @return Crawler
*/
- protected function executeDomQuery($markup, $query)
+ private function executeDomQuery($markup, $query)
{
- return Query::execute(
- $query,
- new Document('' . $markup, Document::DOC_HTML, 'UTF-8'),
- Query::TYPE_CSS
- );
+ $dom = new Crawler($markup);
+
+ return $dom->filter($query);
}
/**
@@ -236,11 +231,11 @@ protected function executeDomQuery($markup, $query)
*
* @throws RiskyTestError When the $attributes array is empty.
*
- * @param array $attributes HTML attributes and their values.
+ * @param array $attributes HTML attributes and their values.
*
* @return string A XPath attribute query selector.
*/
- protected function flattenAttributeArray(array $attributes)
+ private function flattenAttributeArray(array $attributes)
{
if (empty($attributes)) {
throw new RiskyTestError('Attributes array is empty.');
@@ -248,10 +243,10 @@ protected function flattenAttributeArray(array $attributes)
array_walk($attributes, function (&$value, $key) {
// Boolean attributes.
- if (null === $value) {
+ if (empty($value)) {
$value = sprintf('[%s]', $key);
} else {
- $value = sprintf('[%s="%s"]', $key, htmlspecialchars($value));
+ $value = sprintf('[%s="%s"]', $key, htmlspecialchars((string) $value));
}
});
@@ -268,17 +263,21 @@ protected function flattenAttributeArray(array $attributes)
*
* @return string The concatenated innerHTML of any matched selectors.
*/
- protected function getInnerHtmlOfMatchedElements($markup, $query)
+ private function getInnerHtmlOfMatchedElements($markup, $query)
{
$results = $this->executeDomQuery($markup, $query);
$contents = [];
// Loop through results and collect their innerHTML values.
foreach ($results as $result) {
- $document = new DOMDocument();
+ if (!isset($result->firstChild)) {
+ continue;
+ }
+
+ $document = new \DOMDocument();
$document->appendChild($document->importNode($result->firstChild, true));
- $contents[] = trim(html_entity_decode($document->saveHTML()));
+ $contents[] = trim(html_entity_decode((string) $document->saveHTML()));
}
return implode(PHP_EOL, $contents);
diff --git a/tests/MarkupAssertionsTraitTest.php b/tests/MarkupAssertionsTraitTest.php
index c16aae4..2666c8f 100644
--- a/tests/MarkupAssertionsTraitTest.php
+++ b/tests/MarkupAssertionsTraitTest.php
@@ -19,7 +19,7 @@ class MarkupAssertionsTraitTest extends TestCase
* @testdox assertContainsSelector() should find matching selectors
* @dataProvider provideSelectorVariants
*/
- public function assertContainsSelector_should_find_matching_selectors($selector)
+ public function assertContainsSelector_should_find_matching_selectors(string $selector): void
{
$this->assertContainsSelector(
$selector,
@@ -31,7 +31,7 @@ public function assertContainsSelector_should_find_matching_selectors($selector)
* @test
* @testdox assertContainsSelector() should pick up multiple instances of a selector
*/
- public function assertContainsSelector_should_pick_up_multiple_instances()
+ public function assertContainsSelector_should_pick_up_multiple_instances(): void
{
$this->assertContainsSelector(
'a',
@@ -44,8 +44,9 @@ public function assertContainsSelector_should_pick_up_multiple_instances()
* @testdox assertNotContainsSelector() should verify that the given selector does not exist
* @dataProvider provideSelectorVariants
*/
- public function assertNotContainsSelector_should_verify_that_the_given_selector_does_not_exist($selector)
- {
+ public function assertNotContainsSelector_should_verify_that_the_given_selector_does_not_exist(
+ string $selector
+ ): void {
$this->assertNotContainsSelector(
$selector,
'This element has little to do with the link.
'
@@ -56,7 +57,7 @@ public function assertNotContainsSelector_should_verify_that_the_given_selector_
* @test
* @testdox assertSelectorCount() should count the instances of a selector
*/
- public function assertSelectorCount_should_count_the_number_of_instances()
+ public function assertSelectorCount_should_count_the_number_of_instances(): void
{
$this->assertSelectorCount(
3,
@@ -69,7 +70,7 @@ public function assertSelectorCount_should_count_the_number_of_instances()
* @test
* @testdox assertHasElementWithAttributes() should find an element with the given attributes
*/
- public function assertHasElementWithAttributes_should_find_elements_with_matching_attributes()
+ public function assertHasElementWithAttributes_should_find_elements_with_matching_attributes(): void
{
$this->assertHasElementWithAttributes(
[
@@ -85,7 +86,7 @@ public function assertHasElementWithAttributes_should_find_elements_with_matchin
* @testdox assertHasElementWithAttributes() should be able to parse spaces in attribute values
* @ticket https://github.com/stevegrunwell/phpunit-markup-assertions/issues/13
*/
- public function assertHasElementWithAttributes_should_be_able_to_handle_spaces()
+ public function assertHasElementWithAttributes_should_be_able_to_handle_spaces(): void
{
$this->assertHasElementWithAttributes(
[
@@ -99,7 +100,7 @@ public function assertHasElementWithAttributes_should_be_able_to_handle_spaces()
* @test
* @testdox assertNotHasElementWithAttributes() should ensure no element has the provided attributes
*/
- public function assertNotHasElementWithAttributes_should_find_no_elements_with_matching_attributes()
+ public function assertNotHasElementWithAttributes_should_find_no_elements_with_matching_attributes(): void
{
$this->assertNotHasElementWithAttributes(
[
@@ -114,7 +115,7 @@ public function assertNotHasElementWithAttributes_should_find_no_elements_with_m
* @test
* @testdox assertElementContains() should be able to search for a selector
*/
- public function assertElementContains_can_match_a_selector()
+ public function assertElementContains_can_match_a_selector(): void
{
$this->assertElementContains(
'ipsum',
@@ -127,7 +128,7 @@ public function assertElementContains_can_match_a_selector()
* @test
* @testdox assertElementContains() should be able to chain multiple selectors
*/
- public function assertElementContains_can_chain_multiple_selectors()
+ public function assertElementContains_can_chain_multiple_selectors(): void
{
$this->assertElementContains(
'ipsum',
@@ -140,7 +141,7 @@ public function assertElementContains_can_chain_multiple_selectors()
* @test
* @testdox assertElementContains() should scope text to the selected element
*/
- public function assertElementContains_should_scope_matches_to_selector()
+ public function assertElementContains_should_scope_matches_to_selector(): void
{
$this->expectException(AssertionFailedError::class);
$this->expectExceptionMessage('The #main div does not contain the string "ipsum".');
@@ -159,7 +160,7 @@ public function assertElementContains_should_scope_matches_to_selector()
* @dataProvider provideGreetingsInDifferentLanguages
* @ticket https://github.com/stevegrunwell/phpunit-markup-assertions/issues/31
*/
- public function assertElementContains_should_handle_various_character_sets($greeting)
+ public function assertElementContains_should_handle_various_character_sets(string $greeting): void
{
$this->assertElementContains(
$greeting,
@@ -172,7 +173,7 @@ public function assertElementContains_should_handle_various_character_sets($gree
* @test
* @testdox assertElementNotContains() should be able to search for a selector
*/
- public function assertElementNotContains_can_match_a_selector()
+ public function assertElementNotContains_can_match_a_selector(): void
{
$this->assertElementNotContains(
'ipsum',
@@ -187,7 +188,7 @@ public function assertElementNotContains_can_match_a_selector()
* @dataProvider provideGreetingsInDifferentLanguages
* @ticket https://github.com/stevegrunwell/phpunit-markup-assertions/issues/31
*/
- public function assertElementNotContains_should_handle_various_character_sets($greeting)
+ public function assertElementNotContains_should_handle_various_character_sets(string $greeting): void
{
$this->assertElementNotContains(
$greeting,
@@ -200,7 +201,7 @@ public function assertElementNotContains_should_handle_various_character_sets($g
* @test
* @testdox assertElementRegExp() should use regular expression matching
*/
- public function assertElementRegExp_should_use_regular_expression_matching()
+ public function assertElementRegExp_should_use_regular_expression_matching(): void
{
$this->assertElementRegExp(
'/[A-Z0-9-]+/',
@@ -213,7 +214,7 @@ public function assertElementRegExp_should_use_regular_expression_matching()
* @test
* @testdox assertElementRegExp() should be able to search for nested contents
*/
- public function assertElementRegExp_should_be_able_to_match_nested_contents()
+ public function assertElementRegExp_should_be_able_to_match_nested_contents(): void
{
$this->assertElementRegExp(
'/[A-Z]+/',
@@ -226,7 +227,7 @@ public function assertElementRegExp_should_be_able_to_match_nested_contents()
* @test
* @testdox assertElementNotRegExp() should use regular expression matching
*/
- public function testAssertElementNotRegExp()
+ public function testAssertElementNotRegExp(): void
{
$this->assertElementNotRegExp(
'/[0-9-]+/',
@@ -240,8 +241,10 @@ public function testAssertElementNotRegExp()
* @test
* @testdox flattenAttributeArray() should flatten an array of attributes
* @dataProvider provideAttributes
+ *
+ * @param array $attributes
*/
- public function flattenArrayAttribute_should_flatten_arrays_of_attributes($attributes, $expected)
+ public function flattenArrayAttribute_should_flatten_arrays_of_attributes(array $attributes, string $expected): void
{
$method = new \ReflectionMethod($this, 'flattenAttributeArray');
$method->setAccessible(true);
@@ -254,7 +257,7 @@ public function flattenArrayAttribute_should_flatten_arrays_of_attributes($attri
* @testdox flattenAttributeArray() should throw a RiskyTestError if the array is empty
* @dataProvider provideAttributes
*/
- public function flattenAttributeArray_should_throw_a_RiskyTestError_if_given_an_empty_array()
+ public function flattenAttributeArray_should_throw_a_RiskyTestError_if_given_an_empty_array(): void
{
$this->expectException(RiskyTestError::class);
@@ -268,7 +271,11 @@ public function flattenAttributeArray_should_throw_a_RiskyTestError_if_given_an_
* @testdox getInnerHtmlOfMatchedElements() should retrieve the inner HTML
* @dataProvider provideInnerHtml
*/
- public function getInnerHtmlOfMatchedElements_should_retrieve_the_inner_HTML($markup, $selector, $expected) {
+ public function getInnerHtmlOfMatchedElements_should_retrieve_the_inner_HTML(
+ string $markup,
+ string $selector,
+ string $expected
+ ): void {
$method = new \ReflectionMethod($this, 'getInnerHtmlOfMatchedElements');
$method->setAccessible(true);
@@ -277,8 +284,10 @@ public function getInnerHtmlOfMatchedElements_should_retrieve_the_inner_HTML($ma
/**
* Data provider for testFlattenAttributeArray().
+ *
+ * @return array,string}>
*/
- public function provideAttributes()
+ public function provideAttributes(): array
{
return [
'Single attribute' => [
@@ -320,7 +329,7 @@ public function provideAttributes()
*
* @return array>
*/
- public function provideInnerHtml()
+ public function provideInnerHtml(): array
{
return [
'A single match' => [
@@ -346,7 +355,7 @@ public function provideInnerHtml()
*
* @return array>
*/
- public function provideSelectorVariants()
+ public function provideSelectorVariants(): array
{
return [
'Simple tag name' => ['a'],
@@ -364,7 +373,7 @@ public function provideSelectorVariants()
*
* @return array>
*/
- public function provideGreetingsInDifferentLanguages()
+ public function provideGreetingsInDifferentLanguages(): array
{
return [
'Arabic' => ['مرحبا!'],