From 6d6009c0ba2ec8a7f30dcec03cd8a654742a9dd9 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 29 Nov 2023 19:47:03 +0000 Subject: [PATCH 1/8] [7.x] Adds L11 support (#172) * Adds L11 support * Apply fixes from StyleCI * Reverts change * Fixes test suite * Excludes L11 on PHP 8.1 --------- Co-authored-by: StyleCI Bot --- .github/workflows/tests.yml | 5 +- composer.json | 22 +++--- tests/Stubs/OutputStub.php | 143 +++++++++++++++++++++++++----------- 3 files changed, 117 insertions(+), 53 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9515b15..734288b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,10 @@ jobs: fail-fast: true matrix: php: [8.1, 8.2, 8.3] - laravel: [10] + laravel: [10, 11] + exclude: + - php: 8.1 + laravel: 11 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} diff --git a/composer.json b/composer.json index 71e771f..114c227 100644 --- a/composer.json +++ b/composer.json @@ -12,21 +12,21 @@ "require": { "php": "^8.1", "ext-dom": "*", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/http": "^10.0", - "illuminate/support": "^10.0", - "illuminate/testing": "^10.0", + "illuminate/contracts": "^10.0|^11.0", + "illuminate/database": "^10.0|^11.0", + "illuminate/http": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "illuminate/testing": "^10.0|^11.0", "mockery/mockery": "^1.0", "phpunit/phpunit": "^10.0.7", - "symfony/console": "^6.2", - "symfony/css-selector": "^6.2", - "symfony/dom-crawler": "^6.2", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2" + "symfony/console": "^6.2|^7.0", + "symfony/css-selector": "^6.2|^7.0", + "symfony/dom-crawler": "^6.2|^7.0", + "symfony/http-foundation": "^6.2|^7.0", + "symfony/http-kernel": "^6.2|^7.0" }, "require-dev": { - "laravel/framework": "^10.0" + "laravel/framework": "^10.0|^11.0" }, "autoload": { "psr-4": { diff --git a/tests/Stubs/OutputStub.php b/tests/Stubs/OutputStub.php index 8ceaf56..0b1df6f 100644 --- a/tests/Stubs/OutputStub.php +++ b/tests/Stubs/OutputStub.php @@ -2,62 +2,123 @@ namespace Laravel\BrowserKitTesting\Tests\Stubs; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Output\OutputInterface; +use Traversable; -class OutputStub implements OutputInterface -{ - public function write($messages, $newline = false, $options = 0) +if (property_exists(Command::class, 'defaultName')) { + class OutputStub implements OutputInterface { - } + public function write($messages, $newline = false, $options = 0) + { + } - public function writeln($messages, $options = 0) - { - } + public function writeln($messages, $options = 0) + { + } - public function setVerbosity($level) - { - } + public function setVerbosity($level) + { + } - public function getVerbosity(): int - { - return 1; - } + public function getVerbosity(): int + { + return 1; + } - public function isQuiet(): bool - { - return false; - } + public function isQuiet(): bool + { + return false; + } - public function isVerbose(): bool - { - return false; - } + public function isVerbose(): bool + { + return false; + } - public function isVeryVerbose(): bool - { - return false; - } + public function isVeryVerbose(): bool + { + return false; + } - public function isDebug(): bool - { - return false; - } + public function isDebug(): bool + { + return false; + } - public function setDecorated($decorated) - { - } + public function setDecorated($decorated) + { + } - public function isDecorated(): bool - { - return false; - } + public function isDecorated(): bool + { + return false; + } - public function setFormatter(OutputFormatterInterface $formatter) - { - } + public function setFormatter(OutputFormatterInterface $formatter) + { + } - public function getFormatter(): OutputFormatterInterface + public function getFormatter(): OutputFormatterInterface + { + } + } +} else { + class OutputStub implements OutputInterface { + public function write(Traversable|array|string $messages, bool $newline = false, int $options = 0): void + { + } + + public function writeln(Traversable|array|string $messages, int $options = 0): void + { + } + + public function setVerbosity(int $level): void + { + } + + public function getVerbosity(): int + { + return 1; + } + + public function isQuiet(): bool + { + return false; + } + + public function isVerbose(): bool + { + return false; + } + + public function isVeryVerbose(): bool + { + return false; + } + + public function isDebug(): bool + { + return false; + } + + public function setDecorated(bool $decorated): void + { + } + + public function isDecorated(): bool + { + return false; + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + } + + public function getFormatter(): OutputFormatterInterface + { + } } } From 9b9a0884d49bd7375ecb969b14f5e942be081ae3 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 3 Jan 2024 22:40:58 +0000 Subject: [PATCH 2/8] Removes `CreatesApplication` trait (#173) --- README.md | 2 -- src/TestCase.php | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c699767..e7ab139 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,6 @@ use Laravel\BrowserKitTesting\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { - use CreatesApplication; - public $baseUrl = 'http://localhost'; // ... diff --git a/src/TestCase.php b/src/TestCase.php index d202711..e902d6c 100755 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -2,7 +2,9 @@ namespace Laravel\BrowserKitTesting; +use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\Eloquent\Model; +use Illuminate\Foundation\Application; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -12,6 +14,7 @@ use Illuminate\Support\Facades\Facade; use Mockery; use PHPUnit\Framework\TestCase as BaseTestCase; +use RuntimeException; abstract class TestCase extends BaseTestCase { @@ -55,11 +58,22 @@ abstract class TestCase extends BaseTestCase /** * Creates the application. * - * Needs to be implemented by subclasses. - * - * @return \Symfony\Component\HttpKernel\HttpKernelInterface + * @return \Illuminate\Foundation\Application */ - abstract public function createApplication(); + public function createApplication() + { + if (method_exists(Application::class, 'inferBaseDirectory')) { + $app = require Application::inferBaseDirectory().'/bootstrap/app.php'; + + $app->make(Kernel::class)->bootstrap(); + + return $app; + } + + throw new RuntimeException( + 'Unable to guess application base directory. Please use the [Tests\CreatesApplication] trait.', + ); + } /** * Setup the test environment. From 8ebab04e89c6ac04c80786a4b4ed1bdd3fc4e3d8 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 10 Jan 2024 20:07:20 +0000 Subject: [PATCH 3/8] Fixes workflow --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 734288b..f27a1ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,9 +18,9 @@ jobs: matrix: php: [8.1, 8.2, 8.3] laravel: [10, 11] - exclude: - - php: 8.1 - laravel: 11 + exclude: + - php: 8.1 + laravel: 11 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} From c4f9e36b1a4aa6459fb7cf2b15f23b41d0638fad Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 10 Jan 2024 20:09:56 +0000 Subject: [PATCH 4/8] Fixes test suite --- tests/Unit/ImpersonatesUsersTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Unit/ImpersonatesUsersTest.php b/tests/Unit/ImpersonatesUsersTest.php index a2c2a49..21f8292 100644 --- a/tests/Unit/ImpersonatesUsersTest.php +++ b/tests/Unit/ImpersonatesUsersTest.php @@ -31,6 +31,10 @@ public function getAuthPassword() { } + public function getAuthPasswordName() + { + } + public function getRememberToken() { } From ee5f1770a45ccf8fcc4a6222246fc6987347a44f Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 10 Jan 2024 20:12:53 +0000 Subject: [PATCH 5/8] Run tests on develop too --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f27a1ee..9f7408f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - develop - '*.x' pull_request: schedule: From c0d6af5ebcb800d468be17077c7571335fc476a6 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 11 Jan 2024 16:00:32 +0000 Subject: [PATCH 6/8] [develop] Adds PHPUnit 11 support (#175) * Uses attributes * Adds PHPUnit 11 support * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot --- .github/workflows/tests.yml | 10 +- composer.json | 4 +- .../Concerns/FormFieldConstraint.php | 82 +++++++++++ src/Constraints/Concerns/HasElement.php | 99 +++++++++++++ src/Constraints/Concerns/HasInElement.php | 78 +++++++++++ src/Constraints/Concerns/HasLink.php | 116 ++++++++++++++++ src/Constraints/Concerns/HasSource.php | 47 +++++++ src/Constraints/Concerns/HasText.php | 47 +++++++ src/Constraints/Concerns/HasValue.php | 74 ++++++++++ src/Constraints/Concerns/IsChecked.php | 60 ++++++++ src/Constraints/Concerns/IsSelected.php | 130 ++++++++++++++++++ src/Constraints/Concerns/PageConstraint.php | 123 +++++++++++++++++ .../Concerns/ReversePageConstraint.php | 59 ++++++++ src/Constraints/FormFieldConstraint.php | 79 +---------- src/Constraints/HasElement.php | 96 +------------ src/Constraints/HasInElement.php | 75 +--------- src/Constraints/HasLink.php | 113 +-------------- src/Constraints/HasSource.php | 44 +----- src/Constraints/HasText.php | 44 +----- src/Constraints/HasValue.php | 71 +--------- src/Constraints/IsChecked.php | 57 +------- src/Constraints/IsSelected.php | 127 +---------------- src/Constraints/PageConstraint.php | 120 +--------------- src/Constraints/ReversePageConstraint.php | 54 +------- tests/Unit/ImpersonatesUsersTest.php | 5 +- .../Unit/InteractsWithAuthenticationTest.php | 29 ++-- tests/Unit/InteractsWithConsoleTest.php | 5 +- tests/Unit/InteractsWithContainerTest.php | 5 +- tests/Unit/InteractsWithDatabaseTest.php | 13 +- .../InteractsWithExceptionHandlingTest.php | 33 ++--- tests/Unit/InteractsWithPagesTest.php | 125 +++++------------ tests/Unit/InteractsWithSessionTest.php | 41 ++---- tests/Unit/MakesHttpRequestsTest.php | 29 ++-- 33 files changed, 1081 insertions(+), 1013 deletions(-) create mode 100644 src/Constraints/Concerns/FormFieldConstraint.php create mode 100644 src/Constraints/Concerns/HasElement.php create mode 100644 src/Constraints/Concerns/HasInElement.php create mode 100644 src/Constraints/Concerns/HasLink.php create mode 100644 src/Constraints/Concerns/HasSource.php create mode 100644 src/Constraints/Concerns/HasText.php create mode 100644 src/Constraints/Concerns/HasValue.php create mode 100644 src/Constraints/Concerns/IsChecked.php create mode 100644 src/Constraints/Concerns/IsSelected.php create mode 100644 src/Constraints/Concerns/PageConstraint.php create mode 100644 src/Constraints/Concerns/ReversePageConstraint.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f7408f..98c08e1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,13 +17,14 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3] + php: [8.2, 8.3] + phpunit: [10, 11] laravel: [10, 11] exclude: - - php: 8.1 - laravel: 11 + - phpunit: 11 + laravel: 10 - name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} + name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - Laravel ${{ matrix.laravel }} steps: - name: Checkout code @@ -40,6 +41,7 @@ jobs: - name: Install dependencies run: | + composer require phpunit/phpunit:^${{ matrix.phpunit }} --no-update composer require "laravel/framework=^${{ matrix.laravel }}" --no-update composer update --prefer-dist --no-interaction --no-progress diff --git a/composer.json b/composer.json index 114c227..e3310ce 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-dom": "*", "illuminate/contracts": "^10.0|^11.0", "illuminate/database": "^10.0|^11.0", @@ -18,7 +18,7 @@ "illuminate/support": "^10.0|^11.0", "illuminate/testing": "^10.0|^11.0", "mockery/mockery": "^1.0", - "phpunit/phpunit": "^10.0.7", + "phpunit/phpunit": "^10.4|^11.0", "symfony/console": "^6.2|^7.0", "symfony/css-selector": "^6.2|^7.0", "symfony/dom-crawler": "^6.2|^7.0", diff --git a/src/Constraints/Concerns/FormFieldConstraint.php b/src/Constraints/Concerns/FormFieldConstraint.php new file mode 100644 index 0000000..fc83ae7 --- /dev/null +++ b/src/Constraints/Concerns/FormFieldConstraint.php @@ -0,0 +1,82 @@ +selector = $selector; + $this->value = (string) $value; + } + + /** + * Get the valid elements. + * + * Multiple elements should be separated by commas without spaces. + * + * @return string + */ + abstract protected function validElements(); + + /** + * Get the form field. + * + * @param \Symfony\Component\DomCrawler\Crawler $crawler + * @return \Symfony\Component\DomCrawler\Crawler + * + * @throws \PHPUnit\Framework\ExpectationFailedException + */ + protected function field(Crawler $crawler) + { + $field = $crawler->filter(implode(', ', $this->getElements())); + + if ($field->count() > 0) { + return $field; + } + + $this->fail($crawler, sprintf( + 'There is no %s with the name or ID [%s]', + $this->validElements(), $this->selector + )); + } + + /** + * Get the elements relevant to the selector. + * + * @return array + */ + protected function getElements() + { + $name = str_replace('#', '', $this->selector); + + $id = str_replace(['[', ']'], ['\\[', '\\]'], $name); + + return collect(explode(',', $this->validElements()))->map(function ($element) use ($name, $id) { + return "{$element}#{$id}, {$element}[name='{$name}']"; + })->all(); + } +} diff --git a/src/Constraints/Concerns/HasElement.php b/src/Constraints/Concerns/HasElement.php new file mode 100644 index 0000000..e7246b0 --- /dev/null +++ b/src/Constraints/Concerns/HasElement.php @@ -0,0 +1,99 @@ +selector = $selector; + $this->attributes = $attributes; + } + + /** + * Check if the element is found in the given crawler. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return bool + */ + public function matches($crawler): bool + { + $elements = $this->crawler($crawler)->filter($this->selector); + + if ($elements->count() == 0) { + return false; + } + + if (empty($this->attributes)) { + return true; + } + + $elements = $elements->reduce(function ($element) { + return $this->hasAttributes($element); + }); + + return $elements->count() > 0; + } + + /** + * Determines if the given element has the attributes. + * + * @param \Symfony\Component\DomCrawler\Crawler $element + * @return bool + */ + protected function hasAttributes(Crawler $element) + { + foreach ($this->attributes as $name => $value) { + if (is_numeric($name)) { + if (is_null($element->attr($value))) { + return false; + } + } else { + if ($element->attr($name) != $value) { + return false; + } + } + } + + return true; + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString(): string + { + $message = "the element [{$this->selector}]"; + + if (! empty($this->attributes)) { + $message .= ' with the attributes '.json_encode($this->attributes); + } + + return $message; + } +} diff --git a/src/Constraints/Concerns/HasInElement.php b/src/Constraints/Concerns/HasInElement.php new file mode 100644 index 0000000..751e4fd --- /dev/null +++ b/src/Constraints/Concerns/HasInElement.php @@ -0,0 +1,78 @@ +text = $text; + $this->element = $element; + } + + /** + * Check if the source or text is found within the element in the given crawler. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return bool + */ + public function matches($crawler): bool + { + $elements = $this->crawler($crawler)->filter($this->element); + + $pattern = $this->getEscapedPattern($this->text); + + foreach ($elements as $element) { + $element = new Crawler($element); + + if (preg_match("/$pattern/i", $element->html())) { + return true; + } + } + + return false; + } + + /** + * Returns the description of the failure. + * + * @return string + */ + protected function getFailureDescription() + { + return sprintf('[%s] contains %s', $this->element, $this->text); + } + + /** + * Returns the reversed description of the failure. + * + * @return string + */ + protected function getReverseFailureDescription() + { + return sprintf('[%s] does not contain %s', $this->element, $this->text); + } +} diff --git a/src/Constraints/Concerns/HasLink.php b/src/Constraints/Concerns/HasLink.php new file mode 100644 index 0000000..1e81908 --- /dev/null +++ b/src/Constraints/Concerns/HasLink.php @@ -0,0 +1,116 @@ + tag. + * + * @var string|null + */ + protected readonly string|null $url; + + /** + * Create a new constraint instance. + * + * @param string $text + * @param string|null $url + * @return void + */ + public function __construct($text, $url = null) + { + $this->url = $url; + $this->text = $text; + } + + /** + * Check if the link is found in the given crawler. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return bool + */ + public function matches($crawler): bool + { + $links = $this->crawler($crawler)->selectLink($this->text); + + if ($links->count() == 0) { + return false; + } + + // If the URL is null we assume the developer only wants to find a link + // with the given text regardless of the URL. So if we find the link + // we will return true. Otherwise, we will look for the given URL. + if ($this->url == null) { + return true; + } + + $absoluteUrl = $this->absoluteUrl(); + + foreach ($links as $link) { + $linkHref = $link->getAttribute('href'); + + if ($linkHref == $this->url || $linkHref == $absoluteUrl) { + return true; + } + } + + return false; + } + + /** + * Add a root if the URL is relative (helper method of the hasLink function). + * + * @return string + */ + protected function absoluteUrl() + { + if (! Str::startsWith($this->url, ['http', 'https'])) { + return URL::to($this->url); + } + + return $this->url; + } + + /** + * Returns the description of the failure. + * + * @return string + */ + public function getFailureDescription() + { + $description = "has a link with the text [{$this->text}]"; + + if ($this->url) { + $description .= " and the URL [{$this->url}]"; + } + + return $description; + } + + /** + * Returns the reversed description of the failure. + * + * @return string + */ + protected function getReverseFailureDescription() + { + $description = "does not have a link with the text [{$this->text}]"; + + if ($this->url) { + $description .= " and the URL [{$this->url}]"; + } + + return $description; + } +} diff --git a/src/Constraints/Concerns/HasSource.php b/src/Constraints/Concerns/HasSource.php new file mode 100644 index 0000000..10be19f --- /dev/null +++ b/src/Constraints/Concerns/HasSource.php @@ -0,0 +1,47 @@ +source = $source; + } + + /** + * Check if the source is found in the given crawler. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return bool + */ + protected function matches($crawler): bool + { + $pattern = $this->getEscapedPattern($this->source); + + return preg_match("/{$pattern}/i", $this->html($crawler)); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString(): string + { + return "the HTML [{$this->source}]"; + } +} diff --git a/src/Constraints/Concerns/HasText.php b/src/Constraints/Concerns/HasText.php new file mode 100644 index 0000000..53bf717 --- /dev/null +++ b/src/Constraints/Concerns/HasText.php @@ -0,0 +1,47 @@ +text = $text; + } + + /** + * Check if the plain text is found in the given crawler. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return bool + */ + protected function matches($crawler): bool + { + $pattern = $this->getEscapedPattern($this->text); + + return preg_match("/{$pattern}/i", $this->text($crawler)); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString(): string + { + return "the text [{$this->text}]"; + } +} diff --git a/src/Constraints/Concerns/HasValue.php b/src/Constraints/Concerns/HasValue.php new file mode 100644 index 0000000..8b62e9f --- /dev/null +++ b/src/Constraints/Concerns/HasValue.php @@ -0,0 +1,74 @@ +crawler($crawler); + + return $this->getInputOrTextAreaValue($crawler) == $this->value; + } + + /** + * Get the value of an input or textarea. + * + * @param \Symfony\Component\DomCrawler\Crawler $crawler + * @return string + * + * @throws \PHPUnit\Framework\ExpectationFailedException + */ + public function getInputOrTextAreaValue(Crawler $crawler) + { + $field = $this->field($crawler); + + return $field->nodeName() == 'input' + ? $field->attr('value') + : $field->text(); + } + + /** + * Return the description of the failure. + * + * @return string + */ + protected function getFailureDescription() + { + return sprintf( + 'the field [%s] contains the expected value [%s]', + $this->selector, $this->value + ); + } + + /** + * Returns the reversed description of the failure. + * + * @return string + */ + protected function getReverseFailureDescription() + { + return sprintf( + 'the field [%s] does not contain the expected value [%s]', + $this->selector, $this->value + ); + } +} diff --git a/src/Constraints/Concerns/IsChecked.php b/src/Constraints/Concerns/IsChecked.php new file mode 100644 index 0000000..4cf376c --- /dev/null +++ b/src/Constraints/Concerns/IsChecked.php @@ -0,0 +1,60 @@ +crawler($crawler); + + return ! is_null($this->field($crawler)->attr('checked')); + } + + /** + * Return the description of the failure. + * + * @return string + */ + protected function getFailureDescription() + { + return "the checkbox [{$this->selector}] is checked"; + } + + /** + * Returns the reversed description of the failure. + * + * @return string + */ + protected function getReverseFailureDescription() + { + return "the checkbox [{$this->selector}] is not checked"; + } +} diff --git a/src/Constraints/Concerns/IsSelected.php b/src/Constraints/Concerns/IsSelected.php new file mode 100644 index 0000000..4d32853 --- /dev/null +++ b/src/Constraints/Concerns/IsSelected.php @@ -0,0 +1,130 @@ +crawler($crawler); + + return in_array($this->value, $this->getSelectedValue($crawler)); + } + + /** + * Get the selected value of a select field or radio group. + * + * @param \Symfony\Component\DomCrawler\Crawler $crawler + * @return array + * + * @throws \PHPUnit\Framework\ExpectationFailedException + */ + public function getSelectedValue(Crawler $crawler) + { + $field = $this->field($crawler); + + return $field->nodeName() == 'select' + ? $this->getSelectedValueFromSelect($field) + : [$this->getCheckedValueFromRadioGroup($field)]; + } + + /** + * Get the selected value from a select field. + * + * @param \Symfony\Component\DomCrawler\Crawler $select + * @return array + */ + protected function getSelectedValueFromSelect(Crawler $select) + { + $selected = []; + + foreach ($select->children() as $option) { + if ($option->nodeName === 'optgroup') { + foreach ($option->childNodes as $child) { + if ($child->hasAttribute('selected')) { + $selected[] = $this->getOptionValue($child); + } + } + } elseif ($option->hasAttribute('selected')) { + $selected[] = $this->getOptionValue($option); + } + } + + return $selected; + } + + /** + * Get the selected value from an option element. + * + * @param \DOMElement $option + * @return string + */ + protected function getOptionValue(DOMElement $option) + { + if ($option->hasAttribute('value')) { + return $option->getAttribute('value'); + } + + return $option->textContent; + } + + /** + * Get the checked value from a radio group. + * + * @param \Symfony\Component\DomCrawler\Crawler $radioGroup + * @return string|null + */ + protected function getCheckedValueFromRadioGroup(Crawler $radioGroup) + { + foreach ($radioGroup as $radio) { + if ($radio->hasAttribute('checked')) { + return $radio->getAttribute('value'); + } + } + } + + /** + * Returns the description of the failure. + * + * @return string + */ + protected function getFailureDescription() + { + return sprintf( + 'the element [%s] has the selected value [%s]', + $this->selector, $this->value + ); + } + + /** + * Returns the reversed description of the failure. + * + * @return string + */ + protected function getReverseFailureDescription() + { + return sprintf( + 'the element [%s] does not have the selected value [%s]', + $this->selector, $this->value + ); + } +} diff --git a/src/Constraints/Concerns/PageConstraint.php b/src/Constraints/Concerns/PageConstraint.php new file mode 100644 index 0000000..0cadce3 --- /dev/null +++ b/src/Constraints/Concerns/PageConstraint.php @@ -0,0 +1,123 @@ +html() : $crawler; + } + + /** + * Make sure we obtain the HTML from the crawler or the response. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return string + */ + protected function text($crawler) + { + return is_object($crawler) ? $crawler->text() : strip_tags($crawler); + } + + /** + * Create a crawler instance if the given value is not already a Crawler. + * + * @param \Symfony\Component\DomCrawler\Crawler|string $crawler + * @return \Symfony\Component\DomCrawler\Crawler + */ + protected function crawler($crawler) + { + return is_object($crawler) ? $crawler : new Crawler($crawler); + } + + /** + * Get the escaped text pattern for the constraint. + * + * @param string $text + * @return string + */ + protected function getEscapedPattern($text) + { + $rawPattern = preg_quote($text, '/'); + + $escapedPattern = preg_quote(e($text), '/'); + + return $rawPattern == $escapedPattern + ? $rawPattern : "({$rawPattern}|{$escapedPattern})"; + } + + /** + * Throw an exception for the given comparison and test description. + * + * @param mixed $other + * @param string $description + * @param \SebastianBergmann\Comparator\ComparisonFailure|null $comparisonFailure + * @return void + * + * @throws \PHPUnit\Framework\ExpectationFailedException + */ + protected function fail(mixed $other, string $description, ?ComparisonFailure $comparisonFailure = null): never + { + $html = $this->html($other); + + $failureDescription = sprintf( + "%s\n\n\nFailed asserting that %s", + $html, $this->getFailureDescription() + ); + + if (! empty($description)) { + $failureDescription .= ": {$description}"; + } + + if (trim($html) != '') { + $failureDescription .= '. Please check the content above.'; + } else { + $failureDescription .= '. The response is empty.'; + } + + throw new ExpectationFailedException($failureDescription, $comparisonFailure); + } + + /** + * Get the description of the failure. + * + * @return string + */ + protected function getFailureDescription() + { + return 'the page contains '.$this->toString(); + } + + /** + * Returns the reversed description of the failure. + * + * @return string + */ + protected function getReverseFailureDescription() + { + return 'the page does not contain '.$this->toString(); + } + + /** + * Get a string representation of the object. + * + * Placeholder method to avoid forcing definition of this method. + * + * @return string + */ + public function toString(): string + { + return ''; + } +} diff --git a/src/Constraints/Concerns/ReversePageConstraint.php b/src/Constraints/Concerns/ReversePageConstraint.php new file mode 100644 index 0000000..00dea46 --- /dev/null +++ b/src/Constraints/Concerns/ReversePageConstraint.php @@ -0,0 +1,59 @@ +pageConstraint = $pageConstraint; + } + + /** + * Reverse the original page constraint result. + * + * @param \Symfony\Component\DomCrawler\Crawler $crawler + * @return bool + */ + public function matches($crawler): bool + { + return ! (fn () => $this->matches($crawler))->call($this->pageConstraint); + } + + /** + * Get the description of the failure. + * + * This method will attempt to negate the original description. + * + * @return string + */ + protected function getFailureDescription() + { + return $this->pageConstraint->getReverseFailureDescription(); + } + + /** + * Get a string representation of the object. + * + * @return string + */ + public function toString(): string + { + return $this->pageConstraint->toString(); + } +} diff --git a/src/Constraints/FormFieldConstraint.php b/src/Constraints/FormFieldConstraint.php index cf7849c..6be7fdd 100644 --- a/src/Constraints/FormFieldConstraint.php +++ b/src/Constraints/FormFieldConstraint.php @@ -2,81 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Runner\Version; -abstract class FormFieldConstraint extends PageConstraint -{ - /** - * The name or ID of the element. - * - * @var string - */ - protected $selector; - - /** - * The expected value. - * - * @var string - */ - protected $value; - - /** - * Create a new constraint instance. - * - * @param string $selector - * @param mixed $value - * @return void - */ - public function __construct($selector, $value) +if (str_starts_with(Version::series(), '10')) { + abstract class FormFieldConstraint extends PageConstraint { - $this->selector = $selector; - $this->value = (string) $value; + use Concerns\FormFieldConstraint; } - - /** - * Get the valid elements. - * - * Multiple elements should be separated by commas without spaces. - * - * @return string - */ - abstract protected function validElements(); - - /** - * Get the form field. - * - * @param \Symfony\Component\DomCrawler\Crawler $crawler - * @return \Symfony\Component\DomCrawler\Crawler - * - * @throws \PHPUnit\Framework\ExpectationFailedException - */ - protected function field(Crawler $crawler) +} else { + abstract readonly class FormFieldConstraint extends PageConstraint { - $field = $crawler->filter(implode(', ', $this->getElements())); - - if ($field->count() > 0) { - return $field; - } - - $this->fail($crawler, sprintf( - 'There is no %s with the name or ID [%s]', - $this->validElements(), $this->selector - )); - } - - /** - * Get the elements relevant to the selector. - * - * @return array - */ - protected function getElements() - { - $name = str_replace('#', '', $this->selector); - - $id = str_replace(['[', ']'], ['\\[', '\\]'], $name); - - return collect(explode(',', $this->validElements()))->map(function ($element) use ($name, $id) { - return "{$element}#{$id}, {$element}[name='{$name}']"; - })->all(); + use Concerns\FormFieldConstraint; } } diff --git a/src/Constraints/HasElement.php b/src/Constraints/HasElement.php index 65efb6f..cf22cdb 100644 --- a/src/Constraints/HasElement.php +++ b/src/Constraints/HasElement.php @@ -2,98 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Runner\Version; -class HasElement extends PageConstraint -{ - /** - * The name or ID of the element. - * - * @var string - */ - protected $selector; - - /** - * The attributes the element should have. - * - * @var array - */ - protected $attributes; - - /** - * Create a new constraint instance. - * - * @param string $selector - * @param array $attributes - * @return void - */ - public function __construct($selector, array $attributes = []) - { - $this->selector = $selector; - $this->attributes = $attributes; - } - - /** - * Check if the element is found in the given crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - public function matches($crawler): bool - { - $elements = $this->crawler($crawler)->filter($this->selector); - - if ($elements->count() == 0) { - return false; - } - - if (empty($this->attributes)) { - return true; - } - - $elements = $elements->reduce(function ($element) { - return $this->hasAttributes($element); - }); - - return $elements->count() > 0; - } - - /** - * Determines if the given element has the attributes. - * - * @param \Symfony\Component\DomCrawler\Crawler $element - * @return bool - */ - protected function hasAttributes(Crawler $element) +if (str_starts_with(Version::series(), '10')) { + class HasElement extends PageConstraint { - foreach ($this->attributes as $name => $value) { - if (is_numeric($name)) { - if (is_null($element->attr($value))) { - return false; - } - } else { - if ($element->attr($name) != $value) { - return false; - } - } - } - - return true; + use Concerns\HasElement; } - - /** - * Returns a string representation of the object. - * - * @return string - */ - public function toString(): string +} else { + readonly class HasElement extends PageConstraint { - $message = "the element [{$this->selector}]"; - - if (! empty($this->attributes)) { - $message .= ' with the attributes '.json_encode($this->attributes); - } - - return $message; + use Concerns\HasElement; } } diff --git a/src/Constraints/HasInElement.php b/src/Constraints/HasInElement.php index 6ba3689..d5f17b2 100644 --- a/src/Constraints/HasInElement.php +++ b/src/Constraints/HasInElement.php @@ -2,77 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Runner\Version; -class HasInElement extends PageConstraint -{ - /** - * The name or ID of the element. - * - * @var string - */ - protected $element; - - /** - * The text expected to be found. - * - * @var string - */ - protected $text; - - /** - * Create a new constraint instance. - * - * @param string $element - * @param string $text - * @return void - */ - public function __construct($element, $text) +if (str_starts_with(Version::series(), '10')) { + class HasInElement extends PageConstraint { - $this->text = $text; - $this->element = $element; + use Concerns\HasInElement; } - - /** - * Check if the source or text is found within the element in the given crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - public function matches($crawler): bool - { - $elements = $this->crawler($crawler)->filter($this->element); - - $pattern = $this->getEscapedPattern($this->text); - - foreach ($elements as $element) { - $element = new Crawler($element); - - if (preg_match("/$pattern/i", $element->html())) { - return true; - } - } - - return false; - } - - /** - * Returns the description of the failure. - * - * @return string - */ - protected function getFailureDescription() - { - return sprintf('[%s] contains %s', $this->element, $this->text); - } - - /** - * Returns the reversed description of the failure. - * - * @return string - */ - protected function getReverseFailureDescription() +} else { + readonly class HasInElement extends PageConstraint { - return sprintf('[%s] does not contain %s', $this->element, $this->text); + use Concerns\HasInElement; } } diff --git a/src/Constraints/HasLink.php b/src/Constraints/HasLink.php index d09cacb..676185e 100644 --- a/src/Constraints/HasLink.php +++ b/src/Constraints/HasLink.php @@ -2,115 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Illuminate\Support\Facades\URL; -use Illuminate\Support\Str; +use PHPUnit\Runner\Version; -class HasLink extends PageConstraint -{ - /** - * The text expected to be found. - * - * @var string - */ - protected $text; - - /** - * The URL expected to be linked in the tag. - * - * @var string|null - */ - protected $url; - - /** - * Create a new constraint instance. - * - * @param string $text - * @param string|null $url - * @return void - */ - public function __construct($text, $url = null) - { - $this->url = $url; - $this->text = $text; - } - - /** - * Check if the link is found in the given crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - public function matches($crawler): bool +if (str_starts_with(Version::series(), '10')) { + class HasLink extends PageConstraint { - $links = $this->crawler($crawler)->selectLink($this->text); - - if ($links->count() == 0) { - return false; - } - - // If the URL is null we assume the developer only wants to find a link - // with the given text regardless of the URL. So if we find the link - // we will return true. Otherwise, we will look for the given URL. - if ($this->url == null) { - return true; - } - - $absoluteUrl = $this->absoluteUrl(); - - foreach ($links as $link) { - $linkHref = $link->getAttribute('href'); - - if ($linkHref == $this->url || $linkHref == $absoluteUrl) { - return true; - } - } - - return false; + use Concerns\HasLink; } - - /** - * Add a root if the URL is relative (helper method of the hasLink function). - * - * @return string - */ - protected function absoluteUrl() +} else { + readonly class HasLink extends PageConstraint { - if (! Str::startsWith($this->url, ['http', 'https'])) { - return URL::to($this->url); - } - - return $this->url; - } - - /** - * Returns the description of the failure. - * - * @return string - */ - public function getFailureDescription() - { - $description = "has a link with the text [{$this->text}]"; - - if ($this->url) { - $description .= " and the URL [{$this->url}]"; - } - - return $description; - } - - /** - * Returns the reversed description of the failure. - * - * @return string - */ - protected function getReverseFailureDescription() - { - $description = "does not have a link with the text [{$this->text}]"; - - if ($this->url) { - $description .= " and the URL [{$this->url}]"; - } - - return $description; + use Concerns\HasLink; } } diff --git a/src/Constraints/HasSource.php b/src/Constraints/HasSource.php index 438d6c8..51d20a9 100644 --- a/src/Constraints/HasSource.php +++ b/src/Constraints/HasSource.php @@ -2,46 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -class HasSource extends PageConstraint -{ - /** - * The expected HTML source. - * - * @var string - */ - protected $source; +use PHPUnit\Runner\Version; - /** - * Create a new constraint instance. - * - * @param string $source - * @return void - */ - public function __construct($source) +if (str_starts_with(Version::series(), '10')) { + class HasSource extends PageConstraint { - $this->source = $source; + use Concerns\HasSource; } - - /** - * Check if the source is found in the given crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - protected function matches($crawler): bool - { - $pattern = $this->getEscapedPattern($this->source); - - return preg_match("/{$pattern}/i", $this->html($crawler)); - } - - /** - * Returns a string representation of the object. - * - * @return string - */ - public function toString(): string +} else { + readonly class HasSource extends PageConstraint { - return "the HTML [{$this->source}]"; + use Concerns\HasSource; } } diff --git a/src/Constraints/HasText.php b/src/Constraints/HasText.php index 5fdeb9f..ff83f87 100644 --- a/src/Constraints/HasText.php +++ b/src/Constraints/HasText.php @@ -2,46 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -class HasText extends PageConstraint -{ - /** - * The expected text. - * - * @var string - */ - protected $text; +use PHPUnit\Runner\Version; - /** - * Create a new constraint instance. - * - * @param string $text - * @return void - */ - public function __construct($text) +if (str_starts_with(Version::series(), '10')) { + class HasText extends PageConstraint { - $this->text = $text; + use Concerns\HasText; } - - /** - * Check if the plain text is found in the given crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - protected function matches($crawler): bool - { - $pattern = $this->getEscapedPattern($this->text); - - return preg_match("/{$pattern}/i", $this->text($crawler)); - } - - /** - * Returns a string representation of the object. - * - * @return string - */ - public function toString(): string +} else { + readonly class HasText extends PageConstraint { - return "the text [{$this->text}]"; + use Concerns\HasText; } } diff --git a/src/Constraints/HasValue.php b/src/Constraints/HasValue.php index f09bcec..b8401f8 100644 --- a/src/Constraints/HasValue.php +++ b/src/Constraints/HasValue.php @@ -2,73 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Runner\Version; -class HasValue extends FormFieldConstraint -{ - /** - * Get the valid elements. - * - * @return string - */ - protected function validElements() +if (str_starts_with(Version::series(), '10')) { + class HasValue extends FormFieldConstraint { - return 'input,textarea'; + use Concerns\HasValue; } - - /** - * Check if the input contains the expected value. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - public function matches($crawler): bool - { - $crawler = $this->crawler($crawler); - - return $this->getInputOrTextAreaValue($crawler) == $this->value; - } - - /** - * Get the value of an input or textarea. - * - * @param \Symfony\Component\DomCrawler\Crawler $crawler - * @return string - * - * @throws \PHPUnit\Framework\ExpectationFailedException - */ - public function getInputOrTextAreaValue(Crawler $crawler) - { - $field = $this->field($crawler); - - return $field->nodeName() == 'input' - ? $field->attr('value') - : $field->text(); - } - - /** - * Return the description of the failure. - * - * @return string - */ - protected function getFailureDescription() - { - return sprintf( - 'the field [%s] contains the expected value [%s]', - $this->selector, $this->value - ); - } - - /** - * Returns the reversed description of the failure. - * - * @return string - */ - protected function getReverseFailureDescription() +} else { + readonly class HasValue extends FormFieldConstraint { - return sprintf( - 'the field [%s] does not contain the expected value [%s]', - $this->selector, $this->value - ); + use Concerns\HasValue; } } diff --git a/src/Constraints/IsChecked.php b/src/Constraints/IsChecked.php index 01c1325..c22f865 100644 --- a/src/Constraints/IsChecked.php +++ b/src/Constraints/IsChecked.php @@ -2,59 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -class IsChecked extends FormFieldConstraint -{ - /** - * Create a new constraint instance. - * - * @param string $selector - * @return void - */ - public function __construct($selector) - { - $this->selector = $selector; - } - - /** - * Get the valid elements. - * - * @return string - */ - protected function validElements() - { - return "input[type='checkbox']"; - } +use PHPUnit\Runner\Version; - /** - * Determine if the checkbox is checked. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - public function matches($crawler): bool +if (str_starts_with(Version::series(), '10')) { + class IsChecked extends FormFieldConstraint { - $crawler = $this->crawler($crawler); - - return ! is_null($this->field($crawler)->attr('checked')); + use Concerns\IsChecked; } - - /** - * Return the description of the failure. - * - * @return string - */ - protected function getFailureDescription() - { - return "the checkbox [{$this->selector}] is checked"; - } - - /** - * Returns the reversed description of the failure. - * - * @return string - */ - protected function getReverseFailureDescription() +} else { + readonly class IsChecked extends FormFieldConstraint { - return "the checkbox [{$this->selector}] is not checked"; + use Concerns\IsChecked; } } diff --git a/src/Constraints/IsSelected.php b/src/Constraints/IsSelected.php index d228f5b..058265e 100644 --- a/src/Constraints/IsSelected.php +++ b/src/Constraints/IsSelected.php @@ -2,129 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -use DOMElement; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Runner\Version; -class IsSelected extends FormFieldConstraint -{ - /** - * Get the valid elements. - * - * @return string - */ - protected function validElements() +if (str_starts_with(Version::series(), '10')) { + class IsSelected extends FormFieldConstraint { - return 'select,input[type="radio"]'; + use Concerns\IsSelected; } - - /** - * Determine if the select or radio element is selected. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - protected function matches($crawler): bool - { - $crawler = $this->crawler($crawler); - - return in_array($this->value, $this->getSelectedValue($crawler)); - } - - /** - * Get the selected value of a select field or radio group. - * - * @param \Symfony\Component\DomCrawler\Crawler $crawler - * @return array - * - * @throws \PHPUnit\Framework\ExpectationFailedException - */ - public function getSelectedValue(Crawler $crawler) - { - $field = $this->field($crawler); - - return $field->nodeName() == 'select' - ? $this->getSelectedValueFromSelect($field) - : [$this->getCheckedValueFromRadioGroup($field)]; - } - - /** - * Get the selected value from a select field. - * - * @param \Symfony\Component\DomCrawler\Crawler $select - * @return array - */ - protected function getSelectedValueFromSelect(Crawler $select) - { - $selected = []; - - foreach ($select->children() as $option) { - if ($option->nodeName === 'optgroup') { - foreach ($option->childNodes as $child) { - if ($child->hasAttribute('selected')) { - $selected[] = $this->getOptionValue($child); - } - } - } elseif ($option->hasAttribute('selected')) { - $selected[] = $this->getOptionValue($option); - } - } - - return $selected; - } - - /** - * Get the selected value from an option element. - * - * @param \DOMElement $option - * @return string - */ - protected function getOptionValue(DOMElement $option) - { - if ($option->hasAttribute('value')) { - return $option->getAttribute('value'); - } - - return $option->textContent; - } - - /** - * Get the checked value from a radio group. - * - * @param \Symfony\Component\DomCrawler\Crawler $radioGroup - * @return string|null - */ - protected function getCheckedValueFromRadioGroup(Crawler $radioGroup) - { - foreach ($radioGroup as $radio) { - if ($radio->hasAttribute('checked')) { - return $radio->getAttribute('value'); - } - } - } - - /** - * Returns the description of the failure. - * - * @return string - */ - protected function getFailureDescription() - { - return sprintf( - 'the element [%s] has the selected value [%s]', - $this->selector, $this->value - ); - } - - /** - * Returns the reversed description of the failure. - * - * @return string - */ - protected function getReverseFailureDescription() +} else { + readonly class IsSelected extends FormFieldConstraint { - return sprintf( - 'the element [%s] does not have the selected value [%s]', - $this->selector, $this->value - ); + use Concerns\IsSelected; } } diff --git a/src/Constraints/PageConstraint.php b/src/Constraints/PageConstraint.php index 121724b..6165969 100644 --- a/src/Constraints/PageConstraint.php +++ b/src/Constraints/PageConstraint.php @@ -3,122 +3,16 @@ namespace Laravel\BrowserKitTesting\Constraints; use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\ExpectationFailedException; -use SebastianBergmann\Comparator\ComparisonFailure; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Runner\Version; -abstract class PageConstraint extends Constraint -{ - /** - * Make sure we obtain the HTML from the crawler or the response. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return string - */ - protected function html($crawler) +if (str_starts_with(Version::series(), '10')) { + abstract class PageConstraint extends Constraint { - return is_object($crawler) ? $crawler->html() : $crawler; + use Concerns\PageConstraint; } - - /** - * Make sure we obtain the HTML from the crawler or the response. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return string - */ - protected function text($crawler) - { - return is_object($crawler) ? $crawler->text() : strip_tags($crawler); - } - - /** - * Create a crawler instance if the given value is not already a Crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return \Symfony\Component\DomCrawler\Crawler - */ - protected function crawler($crawler) - { - return is_object($crawler) ? $crawler : new Crawler($crawler); - } - - /** - * Get the escaped text pattern for the constraint. - * - * @param string $text - * @return string - */ - protected function getEscapedPattern($text) - { - $rawPattern = preg_quote($text, '/'); - - $escapedPattern = preg_quote(e($text), '/'); - - return $rawPattern == $escapedPattern - ? $rawPattern : "({$rawPattern}|{$escapedPattern})"; - } - - /** - * Throw an exception for the given comparison and test description. - * - * @param mixed $other - * @param string $description - * @param \SebastianBergmann\Comparator\ComparisonFailure|null $comparisonFailure - * @return void - * - * @throws \PHPUnit\Framework\ExpectationFailedException - */ - protected function fail(mixed $other, string $description, ?ComparisonFailure $comparisonFailure = null): never - { - $html = $this->html($other); - - $failureDescription = sprintf( - "%s\n\n\nFailed asserting that %s", - $html, $this->getFailureDescription() - ); - - if (! empty($description)) { - $failureDescription .= ": {$description}"; - } - - if (trim($html) != '') { - $failureDescription .= '. Please check the content above.'; - } else { - $failureDescription .= '. The response is empty.'; - } - - throw new ExpectationFailedException($failureDescription, $comparisonFailure); - } - - /** - * Get the description of the failure. - * - * @return string - */ - protected function getFailureDescription() - { - return 'the page contains '.$this->toString(); - } - - /** - * Returns the reversed description of the failure. - * - * @return string - */ - protected function getReverseFailureDescription() - { - return 'the page does not contain '.$this->toString(); - } - - /** - * Get a string representation of the object. - * - * Placeholder method to avoid forcing definition of this method. - * - * @return string - */ - public function toString(): string +} else { + abstract readonly class PageConstraint extends Constraint { - return ''; + use Concerns\PageConstraint; } } diff --git a/src/Constraints/ReversePageConstraint.php b/src/Constraints/ReversePageConstraint.php index 0117a4c..9031e16 100644 --- a/src/Constraints/ReversePageConstraint.php +++ b/src/Constraints/ReversePageConstraint.php @@ -2,56 +2,16 @@ namespace Laravel\BrowserKitTesting\Constraints; -class ReversePageConstraint extends PageConstraint -{ - /** - * The page constraint instance. - * - * @var \Laravel\BrowserKitTesting\Constraints\PageConstraint - */ - protected $pageConstraint; +use PHPUnit\Runner\Version; - /** - * Create a new reverse page constraint instance. - * - * @param \Laravel\BrowserKitTesting\Constraints\PageConstraint $pageConstraint - * @return void - */ - public function __construct(PageConstraint $pageConstraint) +if (str_starts_with(Version::series(), '10')) { + class ReversePageConstraint extends PageConstraint { - $this->pageConstraint = $pageConstraint; + use Concerns\ReversePageConstraint; } - - /** - * Reverse the original page constraint result. - * - * @param \Symfony\Component\DomCrawler\Crawler $crawler - * @return bool - */ - public function matches($crawler): bool - { - return ! $this->pageConstraint->matches($crawler); - } - - /** - * Get the description of the failure. - * - * This method will attempt to negate the original description. - * - * @return string - */ - protected function getFailureDescription() - { - return $this->pageConstraint->getReverseFailureDescription(); - } - - /** - * Get a string representation of the object. - * - * @return string - */ - public function toString(): string +} else { + readonly class ReversePageConstraint extends PageConstraint { - return $this->pageConstraint->toString(); + use Concerns\ReversePageConstraint; } } diff --git a/tests/Unit/ImpersonatesUsersTest.php b/tests/Unit/ImpersonatesUsersTest.php index 21f8292..9320489 100644 --- a/tests/Unit/ImpersonatesUsersTest.php +++ b/tests/Unit/ImpersonatesUsersTest.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Auth\Authenticatable; use Laravel\BrowserKitTesting\Concerns\ImpersonatesUsers; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; class ImpersonatesUsersTest extends TestCase { @@ -12,9 +13,7 @@ class ImpersonatesUsersTest extends TestCase protected $app; - /** - * @test - */ + #[Test] public function set_currently_logged_in_user_for_app() { $user = new class implements Authenticatable diff --git a/tests/Unit/InteractsWithAuthenticationTest.php b/tests/Unit/InteractsWithAuthenticationTest.php index c997930..64531a3 100644 --- a/tests/Unit/InteractsWithAuthenticationTest.php +++ b/tests/Unit/InteractsWithAuthenticationTest.php @@ -4,6 +4,8 @@ use Laravel\BrowserKitTesting\Concerns\InteractsWithAuthentication; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; class InteractsWithAuthenticationTest extends TestCase { @@ -45,9 +47,7 @@ public function validateCredentials() }; } - /** - * @test - */ + #[Test] public function hasCredentials_return_true_if_the_credentials_are_valid() { $this->app = $this->createUserProviderToCredentials(); @@ -61,11 +61,8 @@ public function hasCredentials_return_true_if_the_credentials_are_valid() $this->assertTrue($this->hasCredentials($credentials)); } - /** - * @test - * - * @dataProvider dataHasCredentials - */ + #[Test] + #[DataProvider('dataHasCredentials')] public function hasCredentials_return_false_if_the_credentials_arent_valid($validateCredentials, $retrieveByCredentials) { $this->app = $this->createUserProviderToCredentials(); @@ -88,9 +85,7 @@ public static function dataHasCredentials() ]; } - /** - * @test - */ + #[Test] public function assert_if_credentials_are_valid_or_invalid() { $this->app = $this->createUserProviderToCredentials(); @@ -107,9 +102,7 @@ public function assert_if_credentials_are_valid_or_invalid() $this->dontSeeCredentials($credentials); } - /** - * @test - */ + #[Test] public function assert_if_user_is_authenticated() { $this->app = new class @@ -149,9 +142,7 @@ public function getAuthIdentifier() $this->seeIsAuthenticatedAs($user); } - /** - * @test - */ + #[Test] public function can_assert_if_someone_is_authenticated() { $this->app = new class @@ -181,9 +172,7 @@ public function check() $this->assertFalse($this->isAuthenticated()); } - /** - * @test - */ + #[Test] public function assert_if_someone_is_authenticated() { $this->app = new class diff --git a/tests/Unit/InteractsWithConsoleTest.php b/tests/Unit/InteractsWithConsoleTest.php index 9afe6f7..929581d 100644 --- a/tests/Unit/InteractsWithConsoleTest.php +++ b/tests/Unit/InteractsWithConsoleTest.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Console\Kernel; use Laravel\BrowserKitTesting\Concerns\InteractsWithConsole; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; class InteractsWithConsoleTest extends TestCase { @@ -12,9 +13,7 @@ class InteractsWithConsoleTest extends TestCase protected $app; - /** - * @test - */ + #[Test] public function call_artisan_command_return_code() { $this->app[Kernel::class] = new class diff --git a/tests/Unit/InteractsWithContainerTest.php b/tests/Unit/InteractsWithContainerTest.php index ada6bd0..c3ff33e 100644 --- a/tests/Unit/InteractsWithContainerTest.php +++ b/tests/Unit/InteractsWithContainerTest.php @@ -4,6 +4,7 @@ use Laravel\BrowserKitTesting\Concerns\InteractsWithContainer; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; class InteractsWithContainerTest extends TestCase { @@ -11,9 +12,7 @@ class InteractsWithContainerTest extends TestCase protected $app; - /** - * @test - */ + #[Test] public function register_instances_of_object_on_container() { $this->app = new class diff --git a/tests/Unit/InteractsWithDatabaseTest.php b/tests/Unit/InteractsWithDatabaseTest.php index 37c9cd5..93777cb 100644 --- a/tests/Unit/InteractsWithDatabaseTest.php +++ b/tests/Unit/InteractsWithDatabaseTest.php @@ -6,6 +6,7 @@ use Laravel\BrowserKitTesting\Concerns\InteractsWithConsole; use Laravel\BrowserKitTesting\Concerns\InteractsWithDatabase; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; class InteractsWithDatabaseTest extends TestCase { @@ -14,9 +15,7 @@ class InteractsWithDatabaseTest extends TestCase protected $app; - /** - * @test - */ + #[Test] public function assert_that_data_exists_on_databases() { $this->app = new class @@ -56,9 +55,7 @@ public function count() $this->seeInDatabase($table, $data); } - /** - * @test - */ + #[Test] public function assert_that_data_not_exists_on_databases() { $this->app = new class @@ -101,9 +98,7 @@ public function count() $this->notSeeInDatabase($table, $data); } - /** - * @test - */ + #[Test] public function run_seed() { $this->app[Kernel::class] = new class diff --git a/tests/Unit/InteractsWithExceptionHandlingTest.php b/tests/Unit/InteractsWithExceptionHandlingTest.php index 828dee8..1ead73d 100644 --- a/tests/Unit/InteractsWithExceptionHandlingTest.php +++ b/tests/Unit/InteractsWithExceptionHandlingTest.php @@ -9,6 +9,7 @@ use Laravel\BrowserKitTesting\Tests\Stubs\ExceptionHandlerStub; use Laravel\BrowserKitTesting\Tests\Stubs\OutputStub; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class InteractsWithExceptionHandlingTest extends TestCase @@ -17,9 +18,7 @@ class InteractsWithExceptionHandlingTest extends TestCase protected $app; - /** - * @test - */ + #[Test] public function withExceptionHandling_restore_exception_handling() { $this->app = new Application(); @@ -31,9 +30,7 @@ public function withExceptionHandling_restore_exception_handling() ); } - /** - * @test - */ + #[Test] public function withoutExceptionHandling_disable_exception_handling_for_the_test() { $this->app = new Application(); @@ -46,9 +43,7 @@ public function withoutExceptionHandling_disable_exception_handling_for_the_test ); } - /** - * @test - */ + #[Test] public function withExceptionHandling_throw_exception_NotFoundHttpException() { $this->expectException(NotFoundHttpException::class); @@ -61,9 +56,7 @@ public function withExceptionHandling_throw_exception_NotFoundHttpException() abort(404, 'Abort 404'); } - /** - * @test - */ + #[Test] public function report_of_instance_ExceptionHandler_on_Application_does_nothing() { $this->app = new Application(); @@ -74,9 +67,7 @@ public function report_of_instance_ExceptionHandler_on_Application_does_nothing( $this->assertNull(app(ExceptionHandler::class)->report(new Exception)); } - /** - * @test - */ + #[Test] public function render_of_instance_ExceptionHandler_on_Application_throw_exception_NotFoundHttpException() { $this->expectException(NotFoundHttpException::class); @@ -108,9 +99,7 @@ public function getCode() app(ExceptionHandler::class)->render($request, new NotFoundHttpException); } - /** - * @test - */ + #[Test] public function render_of_instance_ExceptionHandler_on_Application_throw_exception_anyone() { $this->expectException(Exception::class); @@ -128,9 +117,7 @@ public function render_of_instance_ExceptionHandler_on_Application_throw_excepti app(ExceptionHandler::class)->render($request, new Exception('My Exception')); } - /** - * @test - */ + #[Test] public function renderForConsole_throw_exception_to_console_and_does_nothing() { $this->app = new Application(); @@ -145,9 +132,7 @@ public function renderForConsole_throw_exception_to_console_and_does_nothing() ); } - /** - * @test - */ + #[Test] public function withoutExceptionHandling_doesnt_not_report_exceptions() { $this->app = new Application(); diff --git a/tests/Unit/InteractsWithPagesTest.php b/tests/Unit/InteractsWithPagesTest.php index 8bc59d7..a11ae10 100644 --- a/tests/Unit/InteractsWithPagesTest.php +++ b/tests/Unit/InteractsWithPagesTest.php @@ -7,6 +7,8 @@ use Laravel\BrowserKitTesting\Concerns\InteractsWithPages; use Laravel\BrowserKitTesting\HttpException; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; class InteractsWithPagesTest extends TestCase { @@ -16,9 +18,7 @@ class InteractsWithPagesTest extends TestCase protected $response; protected $currentUri; - /** - * @test - */ + #[Test] public function type_method_write_on_input() { $html = ' @@ -36,9 +36,7 @@ public function type_method_write_on_input() $this->assertSame($this->inputs['name'], $name); } - /** - * @test - */ + #[Test] public function check_method_check_checkbox() { $html = ' @@ -55,9 +53,7 @@ public function check_method_check_checkbox() $this->assertTrue($this->inputs['terms-conditions']); } - /** - * @test - */ + #[Test] public function uncheck_method_uncheck_checkbox() { $html = ' @@ -74,9 +70,7 @@ public function uncheck_method_uncheck_checkbox() $this->assertFalse($this->inputs['terms-conditions']); } - /** - * @test - */ + #[Test] public function select_method_select_an_option_from_drop_down() { $html = ' @@ -98,9 +92,7 @@ public function select_method_select_an_option_from_drop_down() $this->assertSame($this->inputs['role'], $role); } - /** - * @test - */ + #[Test] public function attach_method_attach_a_file() { $html = ' @@ -120,9 +112,7 @@ public function attach_method_attach_a_file() $this->assertSame($this->uploads['avatar'], $avatar); } - /** - * @test - */ + #[Test] public function storeInput_method_store_a_form_input_in_the_local_array() { $html = ' @@ -157,9 +147,7 @@ public function storeInput_method_store_a_form_input_in_the_local_array() $this->assertSame($this->inputs['name'], $name); } - /** - * @test - */ + #[Test] public function when_input_dont_exist_storeInput_throw_exception() { $this->expectException(InvalidArgumentException::class); @@ -181,9 +169,7 @@ public function when_input_dont_exist_storeInput_throw_exception() $this->storeInput('name', 'Taylor'); } - /** - * @test - */ + #[Test] public function getForm_method_returns_Form_from_page_with_the_given_submit_button_text() { $html = ' @@ -199,9 +185,7 @@ public function getForm_method_returns_Form_from_page_with_the_given_submit_butt $this->assertInstanceOf(\Symfony\Component\DomCrawler\Form::class, $this->getForm()); } - /** - * @test - */ + #[Test] public function when_exists_button_getForm_method_throw_exception() { $this->expectException(InvalidArgumentException::class); @@ -219,9 +203,7 @@ public function when_exists_button_getForm_method_throw_exception() $this->assertInstanceOf(\Symfony\Component\DomCrawler\Form::class, $this->getForm('Search')); } - /** - * @test - */ + #[Test] public function fillForm_method_return_Form_with_the_given_data() { $html = ' @@ -238,9 +220,7 @@ public function fillForm_method_return_Form_with_the_given_data() $this->assertSame('Taylor', $form->get('name')->getValue()); } - /** - * @test - */ + #[Test] public function fillForm_method_return_Form_when_given_array_data() { $html = ' @@ -257,9 +237,7 @@ public function fillForm_method_return_Form_when_given_array_data() $this->assertSame('Taylor', $form->get('name')->getValue()); } - /** - * @test - */ + #[Test] public function resetPageContext_method_clear_crawler_subcrawlers() { $body = ' @@ -287,9 +265,7 @@ public function resetPageContext_method_clear_crawler_subcrawlers() }); } - /** - * @test - */ + #[Test] public function clearInputs_method_clear_all_inputs_and_uploads() { $avatar = '/path/to/my-avatar.png'; @@ -305,9 +281,7 @@ public function clearInputs_method_clear_all_inputs_and_uploads() $this->assertEmpty($this->uploads); } - /** - * @test - */ + #[Test] public function extractParametersFromForm_extract_parameter_of_form() { $html = ' @@ -329,9 +303,7 @@ public function extractParametersFromForm_extract_parameter_of_form() ); } - /** - * @test - */ + #[Test] public function convertUploadsForTesting_converter_uploads_to_UploadedFile_instances() { $html = ' @@ -357,9 +329,7 @@ public function convertUploadsForTesting_converter_uploads_to_UploadedFile_insta $this->assertEmpty($uploads['photos'][0]); } - /** - * @test - */ + #[Test] public function assertPageLoaded_check_that_the_page_was_loaded() { $this->app = null; @@ -374,9 +344,7 @@ public function getStatusCode() $this->assertPageLoaded($uri); } - /** - * @test - */ + #[Test] public function assertPageLoaded_throw_exception_when_the_page_was_not_loaded_correctly() { $this->expectException(HttpException::class); @@ -394,9 +362,7 @@ public function getStatusCode() $this->assertPageLoaded($uri); } - /** - * @test - */ + #[Test] public function assertPageLoaded_throw_exception_with_response_exception() { $this->expectException(HttpException::class); @@ -421,9 +387,7 @@ public function getStatusCode() $this->assertPageLoaded($uri); } - /** - * @test - */ + #[Test] public function crawler_method_return_first_subCrawler() { $body = ' @@ -444,11 +408,8 @@ public function crawler_method_return_first_subCrawler() }); } - /** - * @test - * - * @dataProvider attributes_UploadedFile - */ + #[Test] + #[DataProvider('attributes_UploadedFile')] public function create_UploadedFile_for_testing($file, $uploads, $name) { $file = $this->getUploadedFileForTesting( @@ -490,9 +451,7 @@ public static function attributes_UploadedFile() ]; } - /** - * @test - */ + #[Test] public function getUploadedFileForTesting_return_null_if_it_can_not_upload_file() { $this->assertNull( @@ -502,9 +461,7 @@ public function getUploadedFileForTesting_return_null_if_it_can_not_upload_file( ); } - /** - * @test - */ + #[Test] public function see_on_current_HTML() { $body = ' @@ -516,9 +473,7 @@ public function see_on_current_HTML() $this->see('Hello, User'); } - /** - * @test - */ + #[Test] public function see_element_on_current_HTML() { $body = ' @@ -530,9 +485,7 @@ public function see_element_on_current_HTML() $this->seeElement('img', ['src' => 'avatar.png', 'alt' => 'ups']); } - /** - * @test - */ + #[Test] public function count_elements_on_current_HTML() { $body = ' @@ -544,9 +497,7 @@ public function count_elements_on_current_HTML() $this->seeElementCount('.card-user', 2); } - /** - * @test - */ + #[Test] public function see_text_on_current_HTML() { $body = ' @@ -558,9 +509,7 @@ public function see_text_on_current_HTML() $this->seeText('Hello, User'); } - /** - * @test - */ + #[Test] public function see_html_on_element() { $body = ' @@ -572,9 +521,7 @@ public function see_html_on_element() $this->seeInElement('h3', 'Hello, User'); } - /** - * @test - */ + #[Test] public function see_value_on_field() { $body = ' @@ -588,9 +535,7 @@ public function see_value_on_field() $this->seeInField('email', 'john.doe@testing.com'); } - /** - * @test - */ + #[Test] public function see_selected_value_on_select_tag() { $body = ' @@ -606,9 +551,7 @@ public function see_selected_value_on_select_tag() $this->seeIsSelected('role', 'sales'); } - /** - * @test - */ + #[Test] public function is_checked_checkbox() { $body = ' @@ -621,9 +564,7 @@ public function is_checked_checkbox() $this->seeIsChecked('active'); } - /** - * @test - */ + #[Test] public function see_text_on_link() { $body = ' diff --git a/tests/Unit/InteractsWithSessionTest.php b/tests/Unit/InteractsWithSessionTest.php index 9202c1a..7766074 100644 --- a/tests/Unit/InteractsWithSessionTest.php +++ b/tests/Unit/InteractsWithSessionTest.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Application; use Laravel\BrowserKitTesting\Concerns\InteractsWithSession; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; class InteractsWithSessionTest extends TestCase { @@ -12,9 +13,7 @@ class InteractsWithSessionTest extends TestCase protected $app; - /** - * @test - */ + #[Test] public function session_method_can_add_data_on_session() { $this->app['session'] = new class @@ -47,9 +46,7 @@ public function wasCalledPutMethod($times) $this->assertTrue($this->app['session']->wasCalledPutMethod(2)); } - /** - * @test - */ + #[Test] public function withSession_method_can_add_data_on_session() { $this->app['session'] = new class @@ -82,9 +79,7 @@ public function wasCalledPutMethod($times) $this->assertTrue($this->app['session']->wasCalledPutMethod(2, 'put')); } - /** - * @test - */ + #[Test] public function can_start_session() { $this->app['session'] = new class @@ -107,9 +102,7 @@ public function start() $this->assertTrue($this->app['session']->isStarted()); } - /** - * @test - */ + #[Test] public function can_flush_session() { $this->app['session'] = new class @@ -137,9 +130,7 @@ public function isCalledFlushMethod() $this->assertTrue($this->app['session']->isCalledFlushMethod()); } - /** - * @test - */ + #[Test] public function check_if_exists_data_on_session_and_check_exist_key() { $this->app['session'] = new class { @@ -163,9 +154,7 @@ public function has($key) $this->seeInSession('foo'); } - /** - * @test - */ + #[Test] public function check_multi_data_on_session_and_check_multi_keys() { $this->app['session'] = new class { @@ -201,9 +190,7 @@ public function has($key) $this->assertSessionHasAll(['foo', 'unit']); } - /** - * @test - */ + #[Test] public function check_not_exists_key_and_multi_key_on_session() { $this->app['session'] = new class { @@ -219,9 +206,7 @@ public function has($key) $this->assertSessionMissing(['foo', 'bar']); } - /** - * @test - */ + #[Test] public function check_if_exists_errors_on_session() { $this->app['session'] = new class { @@ -241,9 +226,7 @@ public function has($key) $this->assertSessionHasErrors(['foo', 'bar']); } - /** - * @test - */ + #[Test] public function check_if_exists_errors_with_value_on_session() { $this->app = new Application(); @@ -268,9 +251,7 @@ public function has($key) $this->assertSessionHasErrors(['foo' => 'bar']); } - /** - * @test - */ + #[Test] public function check_if_exists_old_input_on_session() { $this->app['session'] = new class { diff --git a/tests/Unit/MakesHttpRequestsTest.php b/tests/Unit/MakesHttpRequestsTest.php index 654d013..ddb42ad 100644 --- a/tests/Unit/MakesHttpRequestsTest.php +++ b/tests/Unit/MakesHttpRequestsTest.php @@ -6,6 +6,8 @@ use Laravel\BrowserKitTesting\Concerns\MakesHttpRequests; use Laravel\BrowserKitTesting\TestResponse; use Laravel\BrowserKitTesting\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\ExpectationFailedException; class MakesHttpRequestsTest extends TestCase @@ -14,11 +16,8 @@ class MakesHttpRequestsTest extends TestCase protected $baseUrl; - /** - * @test - * - * @dataProvider dataUrls - */ + #[Test] + #[DataProvider('dataUrls')] public function prepareUrlForRequest_method_return_all_url($url, $expectedUrl) { $this->baseUrl = 'http://localhost'; @@ -40,9 +39,7 @@ public static function dataUrls() ]; } - /** - * @test - */ + #[Test] public function seeStatusCode_check_status_code() { $this->response = TestResponse::fromBaseResponse(new class extends Response @@ -56,9 +53,7 @@ public function getStatusCode(): int $this->seeStatusCode(200); } - /** - * @test - */ + #[Test] public function assertResponseOk_check_that_the_status_page_should_be_200() { $this->response = TestResponse::fromBaseResponse(new class extends Response @@ -77,9 +72,7 @@ public function isOk(): bool $this->response->assertResponseOk(); } - /** - * @test - */ + #[Test] public function assertResponseOk_throw_exception_when_the_status_page_is_not_200() { $this->expectException(ExpectationFailedException::class); @@ -100,9 +93,7 @@ public function isOk(): bool $this->response->assertResponseOk(); } - /** - * @test - */ + #[Test] public function assertResponseStatus_check_the_response_status_is_equal_to_passed_by_parameter() { $this->response = TestResponse::fromBaseResponse(new class extends Response @@ -116,9 +107,7 @@ public function getStatusCode(): int $this->response->assertResponseStatus(200); } - /** - * @test - */ + #[Test] public function assertResponseStatus_throw_exception_when_the_response_status_is_not_equal_to_passed_by_parameter() { $this->expectException(ExpectationFailedException::class); From dbb23f67e66e20b9cb2e6a305acfb289fe85aa29 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 12 Jan 2024 14:04:47 +0000 Subject: [PATCH 7/8] Reverts `CreatesApplication` change --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e7ab139..c699767 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ use Laravel\BrowserKitTesting\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { + use CreatesApplication; + public $baseUrl = 'http://localhost'; // ... From 4fd90e443f6edfcf68b1246a2080af9bccf315a9 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 12 Jan 2024 14:19:39 +0000 Subject: [PATCH 8/8] Fixes calling protected method --- src/Constraints/Concerns/ReversePageConstraint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Constraints/Concerns/ReversePageConstraint.php b/src/Constraints/Concerns/ReversePageConstraint.php index 00dea46..26142d6 100644 --- a/src/Constraints/Concerns/ReversePageConstraint.php +++ b/src/Constraints/Concerns/ReversePageConstraint.php @@ -44,7 +44,7 @@ public function matches($crawler): bool */ protected function getFailureDescription() { - return $this->pageConstraint->getReverseFailureDescription(); + return (fn () => $this->getReverseFailureDescription())->call($this->pageConstraint); } /**