├── .editorconfig
├── .github
├── CONTRIBUTING.md
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── code-coverage.yml
│ ├── coding-standards.yml
│ ├── static-code-analysis.yml
│ └── unit-tests.yml
├── .gitignore
├── .phpcs.xml.dist
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── _config.yml
├── composer.json
├── phpstan.neon.dist
├── phpunit.xml.dist
├── src
└── MarkupAssertionsTrait.php
└── tests
└── MarkupAssertionsTraitTest.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # PHP PSR-2 Coding Standards
5 | # http://www.php-fig.org/psr/psr-2/
6 |
7 | root = true
8 |
9 | [*]
10 | charset = utf-8
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | [*.php]
15 | end_of_line = lf
16 | indent_style = space
17 | indent_size = 4
18 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to PHPUnit Markup Assertions
2 |
3 | Thank you for your interest in contributing the ongoing development of the PHPUnit Markup Assertion library!
4 |
5 |
6 | ## Contributing code
7 |
8 | Begin by cloning the GitHub repo locally and installing the dependencies with [Composer](https://getcomposer.org):
9 |
10 | ```sh
11 | # Clone the repository + change into the directory
12 | $ git clone https://github.com/stevegrunwell/phpunit-markup-assertions.git \
13 | && cd phpunit-markup-assertions
14 |
15 | # Install local dependencies
16 | $ composer install
17 | ```
18 |
19 |
20 | ### Branching
21 |
22 | Pull requests should be based off the `develop` branch, which represents the current development state of the library. The only thing ever merged into `master` should be new release branches, at the time a release is tagged.
23 |
24 | To create a new feature branch:
25 |
26 | ```bash
27 | # Start on develop, making sure it's up-to-date
28 | $ git checkout develop && git pull
29 |
30 | # Create a new branch for your feature
31 | $ git checkout -b feature/my-cool-new-feature
32 | ```
33 |
34 | When submitting a new pull request, your `feature/my-cool-new-feature` should be compared against `develop`.
35 |
36 |
37 | ### Coding standards
38 |
39 | This project uses [the PSR-2 coding standards](http://www.php-fig.org/psr/psr-2/).
40 |
41 |
42 | ### Running unit tests
43 |
44 | [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:
45 |
46 | ```bash
47 | $ composer test
48 | ```
49 |
50 | #### Code coverage
51 |
52 | [](https://coveralls.io/github/stevegrunwell/phpunit-markup-assertions?branch=develop)
53 |
54 | To generate a report of code coverage for the current branch, you may run the following Composer script, which will generate an HTML report in `tests/coverage/`:
55 |
56 | ```bash
57 | $ composer test-coverage
58 | ```
59 |
60 | Note that [both the Xdebug and tokenizer PHP extensions must be installed and active](https://phpunit.de/manual/current/en/textui.html) on the machine running the tests.
61 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [stevegrunwell]
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: phpunit/phpunit
11 | versions:
12 | - "> 6.0"
13 |
--------------------------------------------------------------------------------
/.github/workflows/code-coverage.yml:
--------------------------------------------------------------------------------
1 | name: Code Coverage
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - develop
8 | - main
9 |
10 | jobs:
11 | coverage:
12 | name: Report code coverage
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 |
18 | - name: Setup PHP
19 | uses: shivammathur/setup-php@v2
20 | with:
21 | php-version: '8.2'
22 | coverage: xdebug
23 |
24 | - name: Install Composer dependencies
25 | uses: ramsey/composer-install@v2
26 |
27 | - name: Run test suite
28 | run: vendor/bin/simple-phpunit --coverage-text --coverage-clover=tests/coverage
29 |
30 | - name: Publish to Coveralls
31 | uses: coverallsapp/github-action@v2
32 | with:
33 | files: tests/coverage
34 | format: clover
35 | fail-on-error: false
36 |
--------------------------------------------------------------------------------
/.github/workflows/coding-standards.yml:
--------------------------------------------------------------------------------
1 | name: Coding Standards
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | phpcs:
7 | name: PHP_CodeSniffer
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v4
12 |
13 | - name: Setup PHP
14 | uses: shivammathur/setup-php@v2
15 | with:
16 | php-version: '8.2'
17 | coverage: none
18 |
19 | - name: Install Composer dependencies
20 | uses: ramsey/composer-install@v2
21 |
22 | - name: Run test suite
23 | run: composer coding-standards
24 |
--------------------------------------------------------------------------------
/.github/workflows/static-code-analysis.yml:
--------------------------------------------------------------------------------
1 | name: Static Code Analysis
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | phpcs:
7 | name: PHPStan
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v4
12 |
13 | - name: Setup PHP
14 | uses: shivammathur/setup-php@v2
15 | with:
16 | php-version: '8.2'
17 | coverage: none
18 |
19 | - name: Install Composer dependencies
20 | uses: ramsey/composer-install@v2
21 |
22 | - name: Run PHPStan
23 | run: composer static-analysis
24 |
--------------------------------------------------------------------------------
/.github/workflows/unit-tests.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | phpunit:
7 | name: PHP ${{ matrix.php-version }}
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup PHP
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: ${{ matrix.php-version }}
20 | coverage: none
21 |
22 | - name: Remove PHPStan as a dependency
23 | run: composer remove --dev phpstan/phpstan
24 |
25 | - name: Install Composer dependencies
26 | uses: ramsey/composer-install@v2
27 |
28 | - name: Run test suite
29 | run: composer test
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | .phpunit.result.cache
3 | .vscode
4 | phpcs.xml
5 | phpstan.neon
6 | phpunit.xml
7 | tests/coverage
8 | vendor
9 |
10 | # The composer.lock file is not needed, as this is a library whose dependencies
11 | # will depend on the version of PHP being used.
12 | composer.lock
13 |
--------------------------------------------------------------------------------
/.phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | Coding standards for PHPUnit Markup Assertions
4 |
5 |
6 |
7 |
8 |
9 |
10 | .
11 | */vendor/*
12 |
13 |
14 |
15 |
16 |
17 |
18 | tests/*
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## [1.4.0] – 2022-12-28
9 |
10 | * Force UTF-8 encoding for better support for non-Latin character sets ([#35])
11 | * Move away from deprecated classes in laminas/laminas-dom ([#32])
12 |
13 | ## [1.3.1] — 2020-01-14
14 |
15 | * Fix PHPUnit warnings regarding `assertContains()` and `assertRegExp()`. Props [@jakobbuis](https://github.com/jakobbuis) ([#20], [#27], [#28])
16 | * Refactor the internal test scaffolding, including a move from Travis CI to GitHub Actions. Props [@peter279k](https://github.com/peter279k) for the assist with GitHub Actions ([#24])
17 | * Added PHP 8.0 support ([#26])
18 |
19 | ## [1.3.0] — 2020-01-27
20 |
21 | * Replace `zendframework/zend-dom` with `laminas/laminas-dom` ([#16])
22 | * Update Composer dependencies, add a `composer test` script ([#15])
23 |
24 |
25 | ## [1.2.0] - 2018-03-27
26 |
27 | * Bumped the minimum version of zendframework/zend-dom to 2.7, which includes a fix for attribute values that include spaces ([#13]).
28 |
29 |
30 | ## [1.1.0] - 2018-01-14
31 |
32 | * Added the `assertElementContains()`, `assertElementNotContains()`, `assertElementRegExp()`, and `assertElementNotRegExp()` assertions, for verifying the contents of elements that match the given DOM query ([#6])
33 | * Moved the `Tests` namespace into a development-only autoloader, to prevent them from potentially being included in projects using this library ([#7])
34 | * [Based on this article by Martin Hujer](https://blog.martinhujer.cz/17-tips-for-using-composer-efficiently/#tip-%236%3A-put-%60composer.lock%60-into-%60.gitignore%60-in-libraries), remove the `composer.lock` file from the library ([#8])
35 | * _Lower_ the minimum version of [zendframework/zend-dom](https://packagist.org/packages/zendframework/zend-dom) to 2.2.5 for maximum portability ([#9])
36 |
37 |
38 | ## [1.0.0] - 2017-10-24
39 |
40 | * Initial release of the PHPUnit Markup Assertions Composer package.
41 |
42 |
43 | [Unreleased]: https://github.com/stevegrunwell/phpunit-markup-assertions/compare/main...develop
44 | [1.4.0]: https://github.com/stevegrunwell/phpunit-markup-assertions/releases/tag/v1.4.0
45 | [1.3.1]: https://github.com/stevegrunwell/phpunit-markup-assertions/releases/tag/v1.3.1
46 | [1.3.0]: https://github.com/stevegrunwell/phpunit-markup-assertions/releases/tag/v1.3.0
47 | [1.2.0]: https://github.com/stevegrunwell/phpunit-markup-assertions/releases/tag/v1.2.0
48 | [1.1.0]: https://github.com/stevegrunwell/phpunit-markup-assertions/releases/tag/v1.1.0
49 | [1.0.0]: https://github.com/stevegrunwell/phpunit-markup-assertions/releases/tag/v1.0.0
50 | [#6]: https://github.com/stevegrunwell/phpunit-markup-assertions/issues/6
51 | [#7]: https://github.com/stevegrunwell/phpunit-markup-assertions/issues/7
52 | [#8]: https://github.com/stevegrunwell/phpunit-markup-assertions/issues/8
53 | [#9]: https://github.com/stevegrunwell/phpunit-markup-assertions/issues/9
54 | [#13]: https://github.com/stevegrunwell/phpunit-markup-assertions/issues/13
55 | [#15]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/15
56 | [#16]: https://github.com/stevegrunwell/phpunit-markup-assertions/issues/16
57 | [#20]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/20
58 | [#24]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/24
59 | [#26]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/26
60 | [#27]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/27
61 | [#28]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/28
62 | [#32]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/32
63 | [#35]: https://github.com/stevegrunwell/phpunit-markup-assertions/pull/35
64 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2017 Steve Grunwell
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHPUnit Markup Assertions
2 |
3 | 
4 | [](https://coveralls.io/github/stevegrunwell/phpunit-markup-assertions?branch=develop)
5 | [](https://github.com/stevegrunwell/phpunit-markup-assertions/releases)
6 |
7 | This library introduces the `MarkupAssertionsTrait` trait for use in [PHPUnit](https://phpunit.de) tests.
8 |
9 | These assertions enable you to inspect generated markup without having to muddy tests with [`DOMDocument`](http://php.net/manual/en/class.domdocument.php) or nasty regular expressions. If you're generating markup at all with PHP, the PHPUnit Markup Assertions trait aims to make the output testable without making your tests fragile.
10 |
11 | ## Example
12 |
13 | ```php
14 | use PHPUnit\Framework\TestCase;
15 | use SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait;
16 |
17 | class MyUnitTest extends TestCase
18 | {
19 | use MarkupAssertionsTrait;
20 |
21 | /**
22 | * Ensure the #first-name and #last-name selectors are present in the form.
23 | */
24 | public function testRenderFormContainsInputs()
25 | {
26 | $markup = render_form();
27 |
28 | $this->assertContainsSelector('#first-name', $markup);
29 | $this->assertContainsSelector('#last-name', $markup);
30 | }
31 | }
32 | ```
33 |
34 | ## Installation
35 |
36 | To add PHPUnit Markup Assertions to your project, first install the library via Composer:
37 |
38 | ```sh
39 | $ composer require --dev stevegrunwell/phpunit-markup-assertions
40 | ```
41 |
42 | Next, import the `SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait` trait into each test case that will leverage the assertions:
43 |
44 | ```php
45 | use PHPUnit\Framework\TestCase;
46 | use SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait;
47 |
48 | class MyTestCase extends TestCase
49 | {
50 | use MarkupAssertionsTrait;
51 | }
52 | ```
53 |
54 | ### Making PHPUnit Markup Assertions available globally
55 |
56 | If you'd like the methods to be available across your entire test suite, you might consider [sub-classing the PHPUnit test case and applying the trait there](https://phpunit.de/manual/current/en/extending-phpunit.html#extending-phpunit.PHPUnit_Framework_TestCase):
57 |
58 | ```php
59 | # tests/TestCase.php
60 |
61 | namespace Tests;
62 |
63 | use PHPUnit\Framework\TestCase as BaseTestCase;
64 | use SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait;
65 |
66 | class TestCase extends BaseTestCase
67 | {
68 | use MarkupAssertionsTrait;
69 | }
70 | ```
71 |
72 | Then update your other test cases to use your new base:
73 |
74 | ```php
75 | # tests/Unit/ExampleTest.php
76 |
77 | namespace Tests/Unit;
78 |
79 | use Tests\TestCase;
80 |
81 | class MyUnitTest extends TestCase
82 | {
83 | // This class now automatically has markup assertions.
84 | }
85 | ```
86 |
87 | ## Available methods
88 |
89 | These are the assertions made available to PHPUnit via the `MarkupAssertionsTrait`.
90 |
91 | * [`assertContainsSelector()`](#assertcontainsselector)
92 | * [`assertNotContainsSelector()`](#assertnotcontainsselector)
93 | * [`assertSelectorCount()`](#assertselectorcount)
94 | * [`assertHasElementWithAttributes()`](#asserthaselementwithattributes)
95 | * [`assertNotHasElementWithAttributes()`](#assertnothaselementwithattributes)
96 | * [`assertElementContains()`](#assertelementcontains)
97 | * [`assertElementNotContains()`](#assertelementnotcontains)
98 | * [`assertElementRegExp()`](#assertelementregexp)
99 | * [`assertElementNotRegExp()`](#assertelementnotregexp)
100 |
101 | ### assertContainsSelector()
102 |
103 | Assert that the given string contains an element matching the given selector.
104 |
105 |
106 |
(string) $selector
107 |
A query selector for the element to find.
108 |
(string) $markup
109 |
The markup that should contain the $selector.
110 |
(string) $message
111 |
A message to display if the assertion fails.
112 |
113 |
114 | #### Example
115 |
116 | ```php
117 | public function testBodyContainsImage()
118 | {
119 | $body = getPageBody();
120 |
121 | $this->assertContainsSelector('img', $body, 'Did not find an image in the page body.');
122 | }
123 | ```
124 |
125 | ### assertNotContainsSelector()
126 |
127 | Assert that the given string does not contain an element matching the given selector.
128 |
129 | This method is the inverse of [`assertContainsSelector()`](#assertcontainsselector).
130 |
131 |
132 |
(string) $selector
133 |
A query selector for the element to find.
134 |
(string) $markup
135 |
The markup that should not contain the $selector.
136 |
(string) $message
137 |
A message to display if the assertion fails.
138 |
139 |
140 | ### assertSelectorCount()
141 |
142 | Assert the number of times an element matching the given selector is found.
143 |
144 |
145 |
(int) $count
146 |
The number of matching elements expected.
147 |
(string) $selector
148 |
A query selector for the element to find.
149 |
(string) $markup
150 |
The markup to run the assertion against.
151 |
(string) $message
152 |
A message to display if the assertion fails.
153 |
154 |
155 | #### Example
156 |
157 | ```php
158 | public function testPostList()
159 | {
160 | factory(Post::class, 10)->create();
161 |
162 | $response = $this->get('/posts');
163 |
164 | $this->assertSelectorCount(10, 'li.post-item', $response->getBody());
165 | }
166 | ```
167 |
168 | ### assertHasElementWithAttributes()
169 |
170 | Assert that an element with the given attributes exists in the given markup.
171 |
172 |
173 |
(array) $attributes
174 |
An array of HTML attributes that should be found on the element.
175 |
(string) $markup
176 |
The markup that should contain an element with the provided $attributes.
177 |
(string) $message
178 |
A message to display if the assertion fails.
179 |
180 |
181 | #### Example
182 |
183 | ```php
184 | public function testExpectedInputsArePresent()
185 | {
186 | $user = getUser();
187 | $form = getFormMarkup();
188 |
189 | $this->assertHasElementWithAttributes(
190 | [
191 | 'name' => 'first-name',
192 | 'value' => $user->first_name,
193 | ],
194 | $form,
195 | 'Did not find the expected input for the user first name.'
196 | );
197 | }
198 | ```
199 |
200 | ### assertNotHasElementWithAttributes()
201 |
202 | Assert that an element with the given attributes does not exist in the given markup.
203 |
204 |
205 |
(array) $attributes
206 |
An array of HTML attributes that should not be found on the element.
207 |
(string) $markup
208 |
The markup that should not contain an element with the provided $attributes.
209 |
(string) $message
210 |
A message to display if the assertion fails.
211 |
212 |
213 | ### assertElementContains()
214 |
215 | Assert that the element with the given selector contains a string.
216 |
217 |
218 |
(string) $contents
219 |
The string to look for within the DOM node's contents.
should contain the user\'s email address.'
241 | );
242 | }
243 | ```
244 |
245 | ### assertElementNotContains()
246 |
247 | Assert that the element with the given selector does not contain a string.
248 |
249 | This method is the inverse of [`assertElementContains()`](#assertelementcontains).
250 |
251 |
252 |
(string) $contents
253 |
The string to look for within the DOM node's contents.
254 |
(string) $selector
255 |
A query selector for the element to find.
256 |
(string) $markup
257 |
The markup that should contain the $selector.
258 |
(string) $message
259 |
A message to display if the assertion fails.
260 |
261 |
262 | ### assertElementRegExp()
263 |
264 | Assert that the element with the given selector contains a string.
265 |
266 | This method works just like [`assertElementContains()`](#assertelementcontains), but uses regular expressions instead of simple string matching.
267 |
268 |
269 |
(string) $regexp
270 |
The regular expression pattern to look for within the DOM node.
271 |
(string) $selector
272 |
A query selector for the element to find.
273 |
(string) $markup
274 |
The markup that should contain the $selector.
275 |
(string) $message
276 |
A message to display if the assertion fails.
277 |
278 |
279 | ### assertElementNotRegExp()
280 |
281 | Assert that the element with the given selector does not contain a string.
282 |
283 | This method is the inverse of [`assertElementRegExp()`](#assertelementregexp) and behaves like [`assertElementNotContains()`](#assertelementnotcontains) except with regular expressions instead of simple string matching.
284 |
285 |
286 |
(string) $regexp
287 |
The regular expression pattern to look for within the DOM node.
288 |
(string) $selector
289 |
A query selector for the element to find.
290 |
(string) $markup
291 |
The markup that should contain the $selector.
292 |
(string) $message
293 |
A message to display if the assertion fails.
294 |
295 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stevegrunwell/phpunit-markup-assertions",
3 | "description": "Assertions for PHPUnit to verify the presence or state of elements within markup",
4 | "keywords": ["phpunit", "testing", "markup", "dom"],
5 | "type": "library",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Steve Grunwell",
10 | "homepage": "https://stevegrunwell.com"
11 | }
12 | ],
13 | "support": {
14 | "issues": "https://github.com/stevegrunwell/phpunit-markup-assertions/issues",
15 | "source": "https://github.com/stevegrunwell/phpunit-markup-assertions/"
16 | },
17 | "require": {
18 | "php": "^5.6 || ^7.0 || ^8.0",
19 | "symfony/css-selector": "^3.4|^4.4|^5.4|^6.0",
20 | "symfony/dom-crawler": "^3.4|^4.4|^5.4|^6.0"
21 | },
22 | "require-dev": {
23 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
24 | "phpcompatibility/php-compatibility": "^9.3",
25 | "phpstan/phpstan": "^1.10",
26 | "squizlabs/php_codesniffer": "^3.7",
27 | "symfony/phpunit-bridge": "^5.2 || ^6.2 || ^7.0"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "SteveGrunwell\\PHPUnit_Markup_Assertions\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "Tests\\": "tests/"
37 | }
38 | },
39 | "scripts": {
40 | "coding-standards": [
41 | "phpcs"
42 | ],
43 | "static-analysis": [
44 | "phpstan analyse"
45 | ],
46 | "test": [
47 | "simple-phpunit --testdox"
48 | ],
49 | "test-coverage": [
50 | "XDEBUG_MODE=coverage ./vendor/bin/simple-phpunit --coverage-html=tests/coverage --colors=always"
51 | ]
52 | },
53 | "scripts-descriptions": {
54 | "coding-standards": "Check coding standards.",
55 | "static-analysis": "Run static code analysis",
56 | "test": "Run all test suites.",
57 | "test-coverage": "Generate code coverage reports in tests/coverage."
58 | },
59 | "config": {
60 | "preferred-install": "dist",
61 | "sort-packages": true,
62 | "allow-plugins": {
63 | "dealerdirect/phpcodesniffer-composer-installer": true
64 | }
65 | },
66 | "archive": {
67 | "exclude": [
68 | "_config.yml",
69 | ".*",
70 | "phpunit.*",
71 | "tests"
72 | ]
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 6
3 | paths:
4 | - src
5 | - tests
6 | excludePaths:
7 | - tests/coverage
8 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests
14 |
15 |
16 |
17 |
18 | ./src
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/MarkupAssertionsTrait.php:
--------------------------------------------------------------------------------
1 | executeDomQuery($markup, $selector);
31 |
32 | $this->assertGreaterThan(0, count($results), $message);
33 | }
34 |
35 | /**
36 | * Assert that the given string does not contain an element matching the given selector.
37 | *
38 | * @since 1.0.0
39 | *
40 | * @param string $selector A query selector for the element to find.
41 | * @param string $markup The output that should not contain the $selector.
42 | * @param string $message A message to display if the assertion fails.
43 | *
44 | * @return void
45 | */
46 | public function assertNotContainsSelector($selector, $markup = '', $message = '')
47 | {
48 | $results = $this->executeDomQuery($markup, $selector);
49 |
50 | $this->assertEquals(0, count($results), $message);
51 | }
52 |
53 | /**
54 | * Assert the number of times an element matching the given selector is found.
55 | *
56 | * @since 1.0.0
57 | *
58 | * @param int $count The number of matching elements expected.
59 | * @param string $selector A query selector for the element to find.
60 | * @param string $markup The markup to run the assertion against.
61 | * @param string $message A message to display if the assertion fails.
62 | *
63 | * @return void
64 | */
65 | public function assertSelectorCount($count, $selector, $markup = '', $message = '')
66 | {
67 | $results = $this->executeDomQuery($markup, $selector);
68 |
69 | $this->assertCount($count, $results, $message);
70 | }
71 |
72 | /**
73 | * Assert that an element with the given attributes exists in the given markup.
74 | *
75 | * @since 1.0.0
76 | *
77 | * @param array $attributes An array of HTML attributes that should be found
78 | * on the element.
79 | * @param string $markup The output that should contain an element with the
80 | * provided $attributes.
81 | * @param string $message A message to display if the assertion fails.
82 | *
83 | * @return void
84 | */
85 | public function assertHasElementWithAttributes($attributes = [], $markup = '', $message = '')
86 | {
87 | $this->assertContainsSelector(
88 | '*' . $this->flattenAttributeArray($attributes),
89 | $markup,
90 | $message
91 | );
92 | }
93 |
94 | /**
95 | * Assert that an element with the given attributes does not exist in the given markup.
96 | *
97 | * @since 1.0.0
98 | *
99 | * @param array $attributes An array of HTML attributes that should be found
100 | * on the element.
101 | * @param string $markup The output that should not contain an element with
102 | * the provided $attributes.
103 | * @param string $message A message to display if the assertion fails.
104 | *
105 | * @return void
106 | */
107 | public function assertNotHasElementWithAttributes($attributes = [], $markup = '', $message = '')
108 | {
109 | $this->assertNotContainsSelector(
110 | '*' . $this->flattenAttributeArray($attributes),
111 | $markup,
112 | $message
113 | );
114 | }
115 |
116 | /**
117 | * Assert an element's contents contain the given string.
118 | *
119 | * @since 1.1.0
120 | *
121 | * @param string $contents The string to look for within the DOM node's contents.
122 | * @param string $selector A query selector for the element to find.
123 | * @param string $markup The output that should contain the $selector.
124 | * @param string $message A message to display if the assertion fails.
125 | *
126 | * @return void
127 | */
128 | public function assertElementContains($contents, $selector = '', $markup = '', $message = '')
129 | {
130 | $method = method_exists($this, 'assertStringContainsString')
131 | ? 'assertStringContainsString'
132 | : 'assertContains'; // @codeCoverageIgnore
133 |
134 | $this->$method(
135 | $contents,
136 | $this->getInnerHtmlOfMatchedElements($markup, $selector),
137 | $message
138 | );
139 | }
140 |
141 | /**
142 | * Assert an element's contents do not contain the given string.
143 | *
144 | * @since 1.1.0
145 | *
146 | * @param string $contents The string to look for within the DOM node's contents.
147 | * @param string $selector A query selector for the element to find.
148 | * @param string $markup The output that should not contain the $selector.
149 | * @param string $message A message to display if the assertion fails.
150 | *
151 | * @return void
152 | */
153 | public function assertElementNotContains($contents, $selector = '', $markup = '', $message = '')
154 | {
155 | $method = method_exists($this, 'assertStringNotContainsString')
156 | ? 'assertStringNotContainsString'
157 | : 'assertNotContains'; // @codeCoverageIgnore
158 |
159 | $this->$method(
160 | $contents,
161 | $this->getInnerHtmlOfMatchedElements($markup, $selector),
162 | $message
163 | );
164 | }
165 |
166 | /**
167 | * Assert an element's contents contain the given regular expression pattern.
168 | *
169 | * @since 1.1.0
170 | *
171 | * @param string $regexp The regular expression pattern to look for within the DOM node.
172 | * @param string $selector A query selector for the element to find.
173 | * @param string $markup The output that should contain the $selector.
174 | * @param string $message A message to display if the assertion fails.
175 | *
176 | * @return void
177 | */
178 | public function assertElementRegExp($regexp, $selector = '', $markup = '', $message = '')
179 | {
180 | $method = method_exists($this, 'assertMatchesRegularExpression')
181 | ? 'assertMatchesRegularExpression'
182 | : 'assertRegExp'; // @codeCoverageIgnore
183 |
184 | $this->$method(
185 | $regexp,
186 | $this->getInnerHtmlOfMatchedElements($markup, $selector),
187 | $message
188 | );
189 | }
190 |
191 | /**
192 | * Assert an element's contents do not contain the given regular expression pattern.
193 | *
194 | * @since 1.1.0
195 | *
196 | * @param string $regexp The regular expression pattern to look for within the DOM node.
197 | * @param string $selector A query selector for the element to find.
198 | * @param string $markup The output that should not contain the $selector.
199 | * @param string $message A message to display if the assertion fails.
200 | *
201 | * @return void
202 | */
203 | public function assertElementNotRegExp($regexp, $selector = '', $markup = '', $message = '')
204 | {
205 | $method = method_exists($this, 'assertDoesNotMatchRegularExpression')
206 | ? 'assertDoesNotMatchRegularExpression'
207 | : 'assertNotRegExp'; // @codeCoverageIgnore
208 |
209 | $this->$method(
210 | $regexp,
211 | $this->getInnerHtmlOfMatchedElements($markup, $selector),
212 | $message
213 | );
214 | }
215 |
216 | /**
217 | * Build a new DOMDocument from the given markup, then execute a query against it.
218 | *
219 | * @since 1.0.0
220 | *
221 | * @param string $markup The HTML for the DOMDocument.
222 | * @param string $query The DOM selector query.
223 | *
224 | * @return Crawler
225 | */
226 | private function executeDomQuery($markup, $query)
227 | {
228 | $dom = new Crawler($markup);
229 |
230 | return $dom->filter($query);
231 | }
232 |
233 | /**
234 | * Given an array of HTML attributes, flatten them into a XPath attribute selector.
235 | *
236 | * @since 1.0.0
237 | *
238 | * @throws RiskyTestError When the $attributes array is empty.
239 | *
240 | * @param array $attributes HTML attributes and their values.
241 | *
242 | * @return string A XPath attribute query selector.
243 | */
244 | private function flattenAttributeArray(array $attributes)
245 | {
246 | if (empty($attributes)) {
247 | throw new RiskyTestError('Attributes array is empty.');
248 | }
249 |
250 | array_walk($attributes, function (&$value, $key) {
251 | // Boolean attributes.
252 | if (null === $value) {
253 | $value = sprintf('[%s]', $key);
254 | } else {
255 | $value = sprintf('[%s="%s"]', $key, htmlspecialchars($value));
256 | }
257 | });
258 |
259 | return implode('', $attributes);
260 | }
261 |
262 | /**
263 | * Given HTML markup and a DOM selector query, collect the innerHTML of the matched selectors.
264 | *
265 | * @since 1.1.0
266 | *
267 | * @param string $markup The HTML for the DOMDocument.
268 | * @param string $query The DOM selector query.
269 | *
270 | * @return string The concatenated innerHTML of any matched selectors.
271 | */
272 | private function getInnerHtmlOfMatchedElements($markup, $query)
273 | {
274 | $results = $this->executeDomQuery($markup, $query);
275 | $contents = [];
276 |
277 | // Loop through results and collect their innerHTML values.
278 | foreach ($results as $result) {
279 | $document = new \DOMDocument();
280 | $document->appendChild($document->importNode($result->firstChild, true));
281 |
282 | $contents[] = trim(html_entity_decode($document->saveHTML()));
283 | }
284 |
285 | return implode(PHP_EOL, $contents);
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/tests/MarkupAssertionsTraitTest.php:
--------------------------------------------------------------------------------
1 | assertContainsSelector(
25 | $selector,
26 | 'Example'
27 | );
28 | }
29 |
30 | /**
31 | * @test
32 | * @testdox assertContainsSelector() should pick up multiple instances of a selector
33 | */
34 | public function assertContainsSelector_should_pick_up_multiple_instances()
35 | {
36 | $this->assertContainsSelector(
37 | 'a',
38 | 'Home | About | Contact'
39 | );
40 | }
41 |
42 | /**
43 | * @test
44 | * @testdox assertNotContainsSelector() should verify that the given selector does not exist
45 | * @dataProvider provideSelectorVariants
46 | */
47 | public function assertNotContainsSelector_should_verify_that_the_given_selector_does_not_exist($selector)
48 | {
49 | $this->assertNotContainsSelector(
50 | $selector,
51 | '
This element has little to do with the link.
'
52 | );
53 | }
54 |
55 | /**
56 | * @test
57 | * @testdox assertSelectorCount() should count the instances of a selector
58 | */
59 | public function assertSelectorCount_should_count_the_number_of_instances()
60 | {
61 | $this->assertSelectorCount(
62 | 3,
63 | 'li',
64 | '
1
2
3
'
65 | );
66 | }
67 |
68 | /**
69 | * @test
70 | * @testdox assertHasElementWithAttributes() should find an element with the given attributes
71 | */
72 | public function assertHasElementWithAttributes_should_find_elements_with_matching_attributes()
73 | {
74 | $this->assertHasElementWithAttributes(
75 | [
76 | 'type' => 'email',
77 | 'value' => 'test@example.com',
78 | ],
79 | ' '
80 | );
81 | }
82 |
83 | /**
84 | * @test
85 | * @testdox assertHasElementWithAttributes() should be able to parse spaces in attribute values
86 | * @ticket https://github.com/stevegrunwell/phpunit-markup-assertions/issues/13
87 | */
88 | public function assertHasElementWithAttributes_should_be_able_to_handle_spaces()
89 | {
90 | $this->assertHasElementWithAttributes(
91 | [
92 | 'data-attr' => 'foo bar baz',
93 | ],
94 | '
Contents
'
95 | );
96 | }
97 |
98 | /**
99 | * @test
100 | * @testdox assertNotHasElementWithAttributes() should ensure no element has the provided attributes
101 | */
102 | public function assertNotHasElementWithAttributes_should_find_no_elements_with_matching_attributes()
103 | {
104 | $this->assertNotHasElementWithAttributes(
105 | [
106 | 'type' => 'email',
107 | 'value' => 'test@example.com',
108 | ],
109 | ' '
110 | );
111 | }
112 |
113 | /**
114 | * @test
115 | * @testdox assertElementContains() should be able to search for a selector
116 | */
117 | public function assertElementContains_can_match_a_selector()
118 | {
119 | $this->assertElementContains(
120 | 'ipsum',
121 | '#main',
122 | 'Lorem ipsum
Lorem ipsum
'
123 | );
124 | }
125 |
126 | /**
127 | * @test
128 | * @testdox assertElementContains() should be able to chain multiple selectors
129 | */
130 | public function assertElementContains_can_chain_multiple_selectors()
131 | {
132 | $this->assertElementContains(
133 | 'ipsum',
134 | '#main .foo',
135 | '
Lorem ipsum
'
136 | );
137 | }
138 |
139 | /**
140 | * @test
141 | * @testdox assertElementContains() should scope text to the selected element
142 | */
143 | public function assertElementContains_should_scope_matches_to_selector()
144 | {
145 | $this->expectException(AssertionFailedError::class);
146 | $this->expectExceptionMessage('The #main div does not contain the string "ipsum".');
147 |
148 | $this->assertElementContains(
149 | 'ipsum',
150 | '#main',
151 | 'Lorem ipsum
Foo bar baz
',
152 | 'The #main div does not contain the string "ipsum".'
153 | );
154 | }
155 |
156 | /**
157 | * @test
158 | * @testdox assertElementContains() should handle various character sets
159 | * @dataProvider provideGreetingsInDifferentLanguages
160 | * @ticket https://github.com/stevegrunwell/phpunit-markup-assertions/issues/31
161 | */
162 | public function assertElementContains_should_handle_various_character_sets($greeting)
163 | {
164 | $this->assertElementContains(
165 | $greeting,
166 | 'h1',
167 | sprintf('
%s
', $greeting)
168 | );
169 | }
170 |
171 | /**
172 | * @test
173 | * @testdox assertElementNotContains() should be able to search for a selector
174 | */
175 | public function assertElementNotContains_can_match_a_selector()
176 | {
177 | $this->assertElementNotContains(
178 | 'ipsum',
179 | '#main',
180 | '