From 26396c24775c5d6c4b34f0a5a25c752f1397cb52 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 10 Jan 2024 17:51:06 +0000 Subject: [PATCH 1/3] Uses attributes --- 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 ++-- 9 files changed, 81 insertions(+), 204 deletions(-) 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 da4e2eb8e8fae426b0a83d9afd11db71b17c1315 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 10 Jan 2024 17:54:05 +0000 Subject: [PATCH 2/3] Adds PHPUnit 11 support --- .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 | 62 +++++++++ src/Constraints/Concerns/IsSelected.php | 131 ++++++++++++++++++ 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 | 119 +--------------- src/Constraints/ReversePageConstraint.php | 54 +------- 24 files changed, 1006 insertions(+), 805 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..dba2d00 --- /dev/null +++ b/src/Constraints/Concerns/IsChecked.php @@ -0,0 +1,62 @@ +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..724d2ed --- /dev/null +++ b/src/Constraints/Concerns/IsSelected.php @@ -0,0 +1,131 @@ +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..ffbd509 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 { + readonly abstract 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..89736cd 100644 --- a/src/Constraints/HasElement.php +++ b/src/Constraints/HasElement.php @@ -2,98 +2,18 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Framework\Constraint\Constraint; -class HasElement extends PageConstraint -{ - /** - * The name or ID of the element. - * - * @var string - */ - protected $selector; +use PHPUnit\Runner\Version; - /** - * 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 = []) +if (str_starts_with(Version::series(), '10')) { + class HasElement extends PageConstraint { - $this->selector = $selector; - $this->attributes = $attributes; + use Concerns\HasElement; } - - /** - * Check if the element is found in the given crawler. - * - * @param \Symfony\Component\DomCrawler\Crawler|string $crawler - * @return bool - */ - public function matches($crawler): bool +} else { + readonly class HasElement extends PageConstraint { - $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; + use Concerns\HasElement; } } diff --git a/src/Constraints/HasInElement.php b/src/Constraints/HasInElement.php index 6ba3689..1937388 100644 --- a/src/Constraints/HasInElement.php +++ b/src/Constraints/HasInElement.php @@ -2,77 +2,18 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Symfony\Component\DomCrawler\Crawler; +use PHPUnit\Framework\Constraint\Constraint; -class HasInElement extends PageConstraint -{ - /** - * The name or ID of the element. - * - * @var string - */ - protected $element; +use PHPUnit\Runner\Version; - /** - * 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) - { - $this->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() +if (str_starts_with(Version::series(), '10')) { + class HasInElement extends PageConstraint { - return sprintf('[%s] contains %s', $this->element, $this->text); + use Concerns\HasInElement; } - - /** - * 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..53184c7 100644 --- a/src/Constraints/HasLink.php +++ b/src/Constraints/HasLink.php @@ -2,115 +2,18 @@ namespace Laravel\BrowserKitTesting\Constraints; -use Illuminate\Support\Facades\URL; -use Illuminate\Support\Str; +use PHPUnit\Framework\Constraint\Constraint; -class HasLink extends PageConstraint -{ - /** - * The text expected to be found. - * - * @var string - */ - protected $text; +use PHPUnit\Runner\Version; - /** - * 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 - { - $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_starts_with(Version::series(), '10')) { + class HasLink extends PageConstraint { - if (! Str::startsWith($this->url, ['http', 'https'])) { - return URL::to($this->url); - } - - return $this->url; + use Concerns\HasLink; } - - /** - * Returns the description of the failure. - * - * @return string - */ - public function getFailureDescription() +} else { + readonly class HasLink extends PageConstraint { - $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..4906d0b 100644 --- a/src/Constraints/PageConstraint.php +++ b/src/Constraints/PageConstraint.php @@ -3,122 +3,17 @@ namespace Laravel\BrowserKitTesting\Constraints; use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\ExpectationFailedException; -use SebastianBergmann\Comparator\ComparisonFailure; -use Symfony\Component\DomCrawler\Crawler; -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) - { - return is_object($crawler) ? $crawler->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.'; - } +use PHPUnit\Runner\Version; - 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() +if (str_starts_with(Version::series(), '10')) { + abstract class PageConstraint extends Constraint { - return 'the page does not contain '.$this->toString(); + use Concerns\PageConstraint; } - - /** - * Get a string representation of the object. - * - * Placeholder method to avoid forcing definition of this method. - * - * @return string - */ - public function toString(): string +} else { + readonly abstract 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; } } From 074e15cf69bfd7919b96880502a47062ef5aba28 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 10 Jan 2024 17:54:30 +0000 Subject: [PATCH 3/3] Apply fixes from StyleCI --- src/Constraints/Concerns/IsChecked.php | 2 -- src/Constraints/Concerns/IsSelected.php | 1 - src/Constraints/FormFieldConstraint.php | 2 +- src/Constraints/HasElement.php | 2 -- src/Constraints/HasInElement.php | 2 -- src/Constraints/HasLink.php | 2 -- src/Constraints/PageConstraint.php | 3 +-- 7 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Constraints/Concerns/IsChecked.php b/src/Constraints/Concerns/IsChecked.php index dba2d00..4cf376c 100644 --- a/src/Constraints/Concerns/IsChecked.php +++ b/src/Constraints/Concerns/IsChecked.php @@ -2,8 +2,6 @@ namespace Laravel\BrowserKitTesting\Constraints\Concerns; -use Laravel\BrowserKitTesting\Constraints\FormFieldConstraint; - trait IsChecked { /** diff --git a/src/Constraints/Concerns/IsSelected.php b/src/Constraints/Concerns/IsSelected.php index 724d2ed..4d32853 100644 --- a/src/Constraints/Concerns/IsSelected.php +++ b/src/Constraints/Concerns/IsSelected.php @@ -3,7 +3,6 @@ namespace Laravel\BrowserKitTesting\Constraints\Concerns; use DOMElement; -use Laravel\BrowserKitTesting\Constraints\FormFieldConstraint; use Symfony\Component\DomCrawler\Crawler; trait IsSelected diff --git a/src/Constraints/FormFieldConstraint.php b/src/Constraints/FormFieldConstraint.php index ffbd509..6be7fdd 100644 --- a/src/Constraints/FormFieldConstraint.php +++ b/src/Constraints/FormFieldConstraint.php @@ -10,7 +10,7 @@ abstract class FormFieldConstraint extends PageConstraint use Concerns\FormFieldConstraint; } } else { - readonly abstract class FormFieldConstraint extends PageConstraint + abstract readonly class FormFieldConstraint extends PageConstraint { use Concerns\FormFieldConstraint; } diff --git a/src/Constraints/HasElement.php b/src/Constraints/HasElement.php index 89736cd..cf22cdb 100644 --- a/src/Constraints/HasElement.php +++ b/src/Constraints/HasElement.php @@ -2,8 +2,6 @@ namespace Laravel\BrowserKitTesting\Constraints; -use PHPUnit\Framework\Constraint\Constraint; - use PHPUnit\Runner\Version; if (str_starts_with(Version::series(), '10')) { diff --git a/src/Constraints/HasInElement.php b/src/Constraints/HasInElement.php index 1937388..d5f17b2 100644 --- a/src/Constraints/HasInElement.php +++ b/src/Constraints/HasInElement.php @@ -2,8 +2,6 @@ namespace Laravel\BrowserKitTesting\Constraints; -use PHPUnit\Framework\Constraint\Constraint; - use PHPUnit\Runner\Version; if (str_starts_with(Version::series(), '10')) { diff --git a/src/Constraints/HasLink.php b/src/Constraints/HasLink.php index 53184c7..676185e 100644 --- a/src/Constraints/HasLink.php +++ b/src/Constraints/HasLink.php @@ -2,8 +2,6 @@ namespace Laravel\BrowserKitTesting\Constraints; -use PHPUnit\Framework\Constraint\Constraint; - use PHPUnit\Runner\Version; if (str_starts_with(Version::series(), '10')) { diff --git a/src/Constraints/PageConstraint.php b/src/Constraints/PageConstraint.php index 4906d0b..6165969 100644 --- a/src/Constraints/PageConstraint.php +++ b/src/Constraints/PageConstraint.php @@ -3,7 +3,6 @@ namespace Laravel\BrowserKitTesting\Constraints; use PHPUnit\Framework\Constraint\Constraint; - use PHPUnit\Runner\Version; if (str_starts_with(Version::series(), '10')) { @@ -12,7 +11,7 @@ abstract class PageConstraint extends Constraint use Concerns\PageConstraint; } } else { - readonly abstract class PageConstraint extends Constraint + abstract readonly class PageConstraint extends Constraint { use Concerns\PageConstraint; }