diff --git a/http-kernel-fixtures/cookie_page2.php b/http-kernel-fixtures/cookie_page2.php index a15faeb..793c204 100644 --- a/http-kernel-fixtures/cookie_page2.php +++ b/http-kernel-fixtures/cookie_page2.php @@ -9,7 +9,7 @@ Previous cookie: cookies->has('srvr_cookie') ? html_escape_value($request->cookies->get('srvr_cookie')) : 'NO'; + echo $request->cookies->has('srvr_cookie') ? html_escape_value($request->cookies->get('srvr_cookie') ?? '') : 'NO'; ?> diff --git a/http-kernel-fixtures/sub-folder/cookie_page1.php b/http-kernel-fixtures/sub-folder/cookie_page1.php index 1588919..ff3a6b5 100644 --- a/http-kernel-fixtures/sub-folder/cookie_page1.php +++ b/http-kernel-fixtures/sub-folder/cookie_page1.php @@ -1,6 +1,7 @@ server->get('REQUEST_URI'); + assert(is_string($requestUri)); $resp = new Symfony\Component\HttpFoundation\Response(); $cook = Symfony\Component\HttpFoundation\Cookie::create('srvr_cookie', 'srv_var_is_set_sub_folder', 0, dirname($requestUri)); $resp->headers->setCookie($cook); diff --git a/http-kernel-fixtures/sub-folder/cookie_page4.php b/http-kernel-fixtures/sub-folder/cookie_page4.php index d3d798d..36b2d8a 100644 --- a/http-kernel-fixtures/sub-folder/cookie_page4.php +++ b/http-kernel-fixtures/sub-folder/cookie_page4.php @@ -1,7 +1,9 @@ server->get('REQUEST_URI')) . '/'; + $requestUri = $request->server->get('REQUEST_URI'); + assert(is_string($requestUri)); + $cookiePath = dirname($requestUri) . '/'; $cookie = Symfony\Component\HttpFoundation\Cookie::create('srvr_cookie', 'srv_var_is_set', 0, $cookiePath); $resp->headers->setCookie($cookie); ?> diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 4d1eb07..96b9a36 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -1,11 +1,10 @@ parameters: - level: 8 + level: 9 paths: - src - tests - http-kernel-fixtures - web-fixtures - checkMissingIterableValueType: false includes: - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/src/FixturesKernel.php b/src/FixturesKernel.php index 324d201..096f9b8 100644 --- a/src/FixturesKernel.php +++ b/src/FixturesKernel.php @@ -2,6 +2,7 @@ namespace Behat\Mink\Tests\Driver\Util; +use Behat\Mink\Tests\Driver\TestCase; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -25,8 +26,8 @@ public function handle(Request $request, $type = 1 /* self::MAIN_REQUEST */ , $c private function handleFixtureRequest(Request $request): Response { - $fixturesDir = realpath(__DIR__ . '/../web-fixtures'); - $overwriteDir = realpath(__DIR__ . '/../http-kernel-fixtures'); + $fixturesDir = realpath(TestCase::WEB_FIXTURES_DIR); + $overwriteDir = realpath(TestCase::KERNEL_FIXTURES_DIR); require_once $fixturesDir . '/utils.php'; @@ -61,7 +62,7 @@ private function prepareSession(Request $request): void $cookies = $request->cookies; if ($cookies->has($session->getName())) { - $session->setId($cookies->get($session->getName())); + $session->setId((string) $cookies->get($session->getName())); } else { $session->migrate(false); } diff --git a/tests/AbstractConfig.php b/tests/AbstractConfig.php index 6b8f952..8999431 100644 --- a/tests/AbstractConfig.php +++ b/tests/AbstractConfig.php @@ -22,7 +22,7 @@ abstract public function createDriver(); */ public function mapRemoteFilePath($file) { - if (!isset($_SERVER['TEST_MACHINE_BASE_PATH']) || !isset($_SERVER['DRIVER_MACHINE_BASE_PATH'])) { + if (!isset($_SERVER['TEST_MACHINE_BASE_PATH'], $_SERVER['DRIVER_MACHINE_BASE_PATH'])) { return $file; } diff --git a/tests/Basic/BasicAuthTest.php b/tests/Basic/BasicAuthTest.php index bc733dc..a947bd3 100644 --- a/tests/Basic/BasicAuthTest.php +++ b/tests/Basic/BasicAuthTest.php @@ -20,12 +20,13 @@ public function testSetBasicAuth(string $user, string $pass, string $pageText): $this->assertStringContainsString($pageText, $session->getPage()->getContent()); } + /** + * @return iterable + */ public static function setBasicAuthDataProvider(): iterable { - return [ - ['mink-user', 'mink-password', 'is authenticated'], - ['', '', 'is not authenticated'], - ]; + yield 'valid credentials' => ['mink-user', 'mink-password', 'is authenticated']; + yield 'no credentials' => ['', '', 'is not authenticated']; } public function testBasicAuthInUrl(): void diff --git a/tests/Basic/BestPracticesTest.php b/tests/Basic/BestPracticesTest.php index b778c18..a1c196d 100644 --- a/tests/Basic/BestPracticesTest.php +++ b/tests/Basic/BestPracticesTest.php @@ -24,9 +24,9 @@ public function testImplementFindXpath(): void { $driver = $this->createDriver(); - $this->assertNotImplementMethod('find', $driver, 'The driver should overwrite `findElementXpaths` rather than `find` for forward compatibility with Mink 2.'); - $this->assertImplementMethod('findElementXpaths', $driver, 'The driver must be able to find elements.'); - $this->assertNotImplementMethod('setSession', $driver, 'The driver should not deal with the Session directly for forward compatibility with Mink 2.'); + $this->assertMethodIsNotImplemented('find', $driver, 'The driver should overwrite `findElementXpaths` rather than `find` for forward compatibility with Mink 2.'); + $this->assertMethodIsImplemented('findElementXpaths', $driver, 'The driver must be able to find elements.'); + $this->assertMethodIsNotImplemented('setSession', $driver, 'The driver should not deal with the Session directly for forward compatibility with Mink 2.'); } /** @@ -36,9 +36,12 @@ public function testImplementBasicApi(string $method): void { $driver = $this->createDriver(); - $this->assertImplementMethod($method, $driver, 'The driver is unusable when this method is not implemented.'); + $this->assertMethodIsImplemented($method, $driver, 'The driver is unusable when this method is not implemented.'); } + /** + * @return iterable + */ public static function provideRequiredMethods(): iterable { return [ @@ -53,7 +56,7 @@ public static function provideRequiredMethods(): iterable ]; } - private function assertImplementMethod(string $method, object $object, string $reason = ''): void + private function assertMethodIsImplemented(string $method, object $object, string $reason = ''): void { $ref = new \ReflectionClass(get_class($object)); $refMethod = $ref->getMethod($method); @@ -67,7 +70,7 @@ private function assertImplementMethod(string $method, object $object, string $r $this->assertNotSame(CoreDriver::class, $refMethod->getDeclaringClass()->name, $message); } - private function assertNotImplementMethod(string $method, object $object, string $reason = ''): void + private function assertMethodIsNotImplemented(string $method, object $object, string $reason = ''): void { $ref = new \ReflectionClass(get_class($object)); $refMethod = $ref->getMethod($method); diff --git a/tests/Basic/ContentTest.php b/tests/Basic/ContentTest.php index 5cd6706..ba09f3f 100644 --- a/tests/Basic/ContentTest.php +++ b/tests/Basic/ContentTest.php @@ -61,6 +61,9 @@ public function testGetAttribute(string $attributeName, ?string $attributeValue) $this->assertSame($attributeValue, $element->getAttribute($attributeName)); } + /** + * @return iterable + */ public static function getAttributeDataProvider(): iterable { return [ diff --git a/tests/Basic/CookieTest.php b/tests/Basic/CookieTest.php index bc4a31d..4c8b568 100644 --- a/tests/Basic/CookieTest.php +++ b/tests/Basic/CookieTest.php @@ -4,6 +4,9 @@ use Behat\Mink\Tests\Driver\TestCase; +/** + * @phpstan-type TCookieRemovalMode 'session_reset'|'cookie_delete' + */ final class CookieTest extends TestCase { /** @@ -87,6 +90,9 @@ public function testCookieWithPaths(string $cookieRemovalMode): void $this->assertStringContainsString('Previous cookie: NO', $session->getPage()->getText()); } + /** + * @phpstan-return iterable + */ public static function cookieWithPathsDataProvider(): iterable { return [ @@ -96,6 +102,7 @@ public static function cookieWithPathsDataProvider(): iterable } /** + * @phpstan-param TCookieRemovalMode $cookieRemovalMode * @dataProvider cookieWithPathsDataProvider */ public function testCookieInSubPath(string $cookieRemovalMode): void diff --git a/tests/Basic/IFrameTest.php b/tests/Basic/IFrameTest.php index 24f14e1..99f9d27 100644 --- a/tests/Basic/IFrameTest.php +++ b/tests/Basic/IFrameTest.php @@ -29,13 +29,11 @@ public function testIFrame(string $iframeIdentifier, string $elementSelector, st } /** - * @return array + * @return iterable */ - public static function iFrameDataProvider() + public static function iFrameDataProvider(): iterable { - return array( - 'by name' => array('subframe_by_name', '#text', 'iFrame div text'), - 'by id' => array('subframe_by_id', '#foobar', 'Some accentués characters'), - ); + yield 'by name' => ['subframe_by_name', '#text', 'iFrame div text']; + yield 'by id' => ['subframe_by_id', '#foobar', 'Some accentués characters']; } } diff --git a/tests/Form/GeneralTest.php b/tests/Form/GeneralTest.php index e0a7b5a..13719fb 100644 --- a/tests/Form/GeneralTest.php +++ b/tests/Form/GeneralTest.php @@ -77,14 +77,15 @@ public function testFormSubmitWays(string $submitVia): void } } + /** + * @return iterable + */ public static function formSubmitWaysDataProvider(): iterable { - return [ - ['Save'], - ['input-type-image'], - ['button-without-type'], - ['button-type-submit'], - ]; + yield ['Save']; + yield ['input-type-image']; + yield ['button-without-type']; + yield ['button-type-submit']; } public function testFormSubmit(): void @@ -189,7 +190,7 @@ public function testAdvancedForm(): void $notes->setValue('new notes'); $this->assertEquals('new notes', $notes->getValue()); - $about->attachFile($this->mapRemoteFilePath(__DIR__ . '/../../web-fixtures/some_file.txt')); + $about->attachFile($this->mapRemoteFilePath(self::WEB_FIXTURES_DIR . '/some_file.txt')); $button = $page->findButton('Register'); $this->assertNotNull($button); @@ -352,7 +353,7 @@ public function testSubmitEmptyTextarea(): void /** * @dataProvider provideInvalidValues * - * @param mixed $value + * @param array|bool|string $value */ public function testSetInvalidValueInField(string $field, $value): void { @@ -366,6 +367,9 @@ public function testSetInvalidValueInField(string $field, $value): void $color->setValue($value); } + /** + * @return iterable + */ public static function provideInvalidValues(): iterable { $trueValue = ['true', true]; diff --git a/tests/Form/Html5Test.php b/tests/Form/Html5Test.php index 74b726c..4f80fa3 100644 --- a/tests/Form/Html5Test.php +++ b/tests/Form/Html5Test.php @@ -163,7 +163,7 @@ public function testHtml5FormMethod(): void /** * @dataProvider provideInvalidValues * - * @param mixed $value + * @param array|bool|string $value */ public function testSetInvalidValueInField(string $field, $value): void { @@ -177,6 +177,9 @@ public function testSetInvalidValueInField(string $field, $value): void $color->setValue($value); } + /** + * @return iterable + */ public static function provideInvalidValues(): iterable { $trueValue = ['true', true]; diff --git a/tests/Form/RadioTest.php b/tests/Form/RadioTest.php index 711ba20..0d940ad 100644 --- a/tests/Form/RadioTest.php +++ b/tests/Form/RadioTest.php @@ -112,6 +112,9 @@ public function testSetBooleanValue(bool $value): void $option->setValue($value); } + /** + * @return iterable + */ public static function provideBooleanValues(): iterable { yield [true]; diff --git a/tests/Form/SelectTest.php b/tests/Form/SelectTest.php index 8ec5471..efe92e9 100644 --- a/tests/Form/SelectTest.php +++ b/tests/Form/SelectTest.php @@ -78,6 +78,9 @@ public function testElementSelectedStateCheck(string $selectName, string $option $this->assertTrue($option->isSelected()); } + /** + * @return iterable + */ public static function elementSelectedStateCheckDataProvider(): iterable { return [ @@ -119,6 +122,9 @@ public function testSetBooleanValue(bool $value): void $select->setValue($value); } + /** + * @return iterable + */ public static function provideBooleanValues(): iterable { yield [true]; diff --git a/tests/Js/ChangeEventTest.php b/tests/Js/ChangeEventTest.php index abe1e66..4d9089a 100644 --- a/tests/Js/ChangeEventTest.php +++ b/tests/Js/ChangeEventTest.php @@ -63,20 +63,18 @@ public function testSetValueChangeEvent(string $elementId, string $valueForEmpty } } + /** + * @return iterable + */ public static function setValueChangeEventDataProvider(): iterable { - $file1 = __DIR__ . '/../../web-fixtures/file1.txt'; - $file2 = __DIR__ . '/../../web-fixtures/file2.txt'; - - return [ - 'input default' => ['the-input-default', 'from empty', 'from existing'], - 'input text' => ['the-input-text', 'from empty', 'from existing'], - 'input email' => ['the-email', 'from empty', 'from existing'], - 'textarea' => ['the-textarea', 'from empty', 'from existing'], - 'file' => ['the-file', $file1, $file2], - 'select' => ['the-select', '30'], - 'radio' => ['the-radio-m', 'm'], - ]; + yield 'input default' => ['the-input-default', 'from empty', 'from existing']; + yield 'input text' => ['the-input-text', 'from empty', 'from existing']; + yield 'input email' => ['the-email', 'from empty', 'from existing']; + yield 'textarea' => ['the-textarea', 'from empty', 'from existing']; + yield 'file' => ['the-file', self::WEB_FIXTURES_DIR . '/file1.txt', self::WEB_FIXTURES_DIR . '/file2.txt']; + yield 'select' => ['the-select', '30', '']; + yield 'radio' => ['the-radio-m', 'm', '']; } /** @@ -95,12 +93,13 @@ public function testSelectOptionChangeEvent(string $elementId, string $elementVa $this->assertElementChangeCount($elementId); } + /** + * @return iterable + */ public static function selectOptionChangeEventDataProvider(): iterable { - return [ - 'select' => ['the-select', '30'], - 'radio' => ['the-radio-m', 'm'], - ]; + yield 'select' => ['the-select', '30']; + yield 'radio' => ['the-radio-m', 'm']; } /** @@ -145,12 +144,13 @@ public function testUncheckChangeEvent(bool $useSetValue): void $this->assertElementChangeCount('the-checked-checkbox'); } + /** + * @return iterable + */ public static function checkboxTestWayDataProvider(): iterable { - return [ - [true], - [false], - ]; + yield [true]; + yield [false]; } private function assertElementChangeCount(string $elementId, string $message = ''): void diff --git a/tests/Js/EventsTest.php b/tests/Js/EventsTest.php index ce681b0..d7495f1 100644 --- a/tests/Js/EventsTest.php +++ b/tests/Js/EventsTest.php @@ -118,15 +118,16 @@ public function testKeyboardEvents(?string $modifier, string $eventProperties): $this->assertEquals('key upped:78 / ' . $eventProperties, $event->getText()); } + /** + * @return iterable + */ public static function provideKeyboardEventsModifiers(): iterable { - return [ - 'none' => [null, '0 / 0 / 0 / 0'], - 'alt' => [KeyModifier::ALT, '1 / 0 / 0 / 0'], - // jQuery considers ctrl as being a metaKey in the normalized event - 'ctrl' => [KeyModifier::CTRL, '0 / 1 / 0 / 1'], - 'shift' => [KeyModifier::SHIFT, '0 / 0 / 1 / 0'], - 'meta' => [KeyModifier::META, '0 / 0 / 0 / 1'], - ]; + yield 'none' => [null, '0 / 0 / 0 / 0']; + yield 'alt' => [KeyModifier::ALT, '1 / 0 / 0 / 0']; + // jQuery considers ctrl as being a metaKey in the normalized event + yield 'ctrl' => [KeyModifier::CTRL, '0 / 1 / 0 / 1']; + yield 'shift' => [KeyModifier::SHIFT, '0 / 0 / 1 / 0']; + yield 'meta' => [KeyModifier::META, '0 / 0 / 0 / 1']; } } diff --git a/tests/Js/JavascriptEvaluationTest.php b/tests/Js/JavascriptEvaluationTest.php index f12a2a8..39d53d3 100644 --- a/tests/Js/JavascriptEvaluationTest.php +++ b/tests/Js/JavascriptEvaluationTest.php @@ -6,9 +6,6 @@ final class JavascriptEvaluationTest extends TestCase { - /** - * Tests, that `wait` method returns check result after exit. - */ public function testWaitReturnValue(): void { $this->getSession()->visit($this->pathTo('/js_test.html')); @@ -55,16 +52,17 @@ public function testExecuteScript(string $script): void $this->assertEquals('Hello world', $heading->getText()); } + /** + * @return iterable + */ public static function provideExecutedScript(): iterable { - return [ - ['document.querySelector("h1").textContent = "Hello world"'], - ['document.querySelector("h1").textContent = "Hello world";'], - ['function () {document.querySelector("h1").textContent = "Hello world";}()'], - ['function () {document.querySelector("h1").textContent = "Hello world";}();'], - ['(function () {document.querySelector("h1").textContent = "Hello world";})()'], - ['(function () {document.querySelector("h1").textContent = "Hello world";})();'], - ]; + yield ['document.querySelector("h1").textContent = "Hello world"']; + yield ['document.querySelector("h1").textContent = "Hello world";']; + yield ['function () {document.querySelector("h1").textContent = "Hello world";}()']; + yield ['function () {document.querySelector("h1").textContent = "Hello world";}();']; + yield ['(function () {document.querySelector("h1").textContent = "Hello world";})()']; + yield ['(function () {document.querySelector("h1").textContent = "Hello world";})();']; } /** @@ -77,17 +75,18 @@ public function testEvaluateJavascript(string $script): void $this->assertSame(2, $this->getSession()->evaluateScript($script)); } + /** + * @return iterable + */ public static function provideEvaluatedScript(): iterable { - return [ - ['1 + 1'], - ['1 + 1;'], - ['return 1 + 1'], - ['return 1 + 1;'], - ['function () {return 1+1;}()'], - ['(function () {return 1+1;})()'], - ['return function () { return 1+1;}()'], - ['return (function () {return 1+1;})()'], - ]; + yield ['1 + 1']; + yield ['1 + 1;']; + yield ['return 1 + 1']; + yield ['return 1 + 1;']; + yield ['function () {return 1+1;}()']; + yield ['(function () {return 1+1;})()']; + yield ['return function () { return 1+1;}()']; + yield ['return (function () {return 1+1;})()']; } } diff --git a/tests/Js/SessionResetTest.php b/tests/Js/SessionResetTest.php index 473a513..c977527 100644 --- a/tests/Js/SessionResetTest.php +++ b/tests/Js/SessionResetTest.php @@ -15,7 +15,7 @@ public function testSessionResetClosesWindows(?string $initialWindowName): void $session->visit($this->pathTo('/window.html')); if (null !== $initialWindowName) { - $session->executeScript('window.name = "'.$initialWindowName.'";'); + $session->executeScript('window.name = "' . $initialWindowName . '";'); } $page = $session->getPage(); @@ -37,12 +37,13 @@ public function testSessionResetClosesWindows(?string $initialWindowName): void $this->assertEquals($expectedInitialWindowName, $actualInitialWindowName, 'Not inside an initial window.'); } - public static function initialWindowNameDataProvider(): array + /** + * @return iterable + */ + public static function initialWindowNameDataProvider(): iterable { - return array( - 'no name' => array(null), - 'non-empty name' => array('initial-window'), - ); + yield 'no name' => [null]; + yield 'non-empty name' => ['initial-window']; } /** diff --git a/tests/TestCase.php b/tests/TestCase.php index d97f6f0..374a2c3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,15 +11,20 @@ abstract class TestCase extends BaseTestCase { + public const WEB_FIXTURES_DIR = __DIR__ . '/../web-fixtures'; + public const KERNEL_FIXTURES_DIR = __DIR__ . '/../http-kernel-fixtures'; + private const DRIVER_CONFIG_FACTORY_KEY = 'driver_config_factory'; + private const MINK_SESSION_KEY = 'sess'; + /** * Mink session manager. * - * @var Mink + * @var Mink|null */ private static $mink; /** - * @var AbstractConfig + * @var AbstractConfig|null */ private static $config; @@ -32,26 +37,29 @@ public static function prepareSession() { if (null === self::$mink) { $session = new Session(self::getConfig()->createDriver()); - self::$mink = new Mink(['sess' => $session]); + self::$mink = new Mink([self::MINK_SESSION_KEY => $session]); } } /** - * @return AbstractConfig - * - * @throws \UnexpectedValueException if the global driver_config_factory returns an invalid object + * @throws \UnexpectedValueException */ - private static function getConfig(): AbstractConfig + private static function createConfig(): AbstractConfig { - if (null === self::$config) { - self::$config = call_user_func($GLOBALS['driver_config_factory']); - - if (!self::$config instanceof AbstractConfig) { - throw new \UnexpectedValueException('The "driver_config_factory" global variable must return a \Behat\Mink\Tests\Driver\AbstractConfig.'); - } + $config = call_user_func($GLOBALS[self::DRIVER_CONFIG_FACTORY_KEY]); + if (!$config instanceof AbstractConfig) { + throw new \UnexpectedValueException(sprintf( + 'The "%s" global variable must return a %s.', + self::DRIVER_CONFIG_FACTORY_KEY, + AbstractConfig::class + )); } + return $config; + } - return self::$config; + private static function getConfig(): AbstractConfig + { + return self::$config ?? (self::$config = self::createConfig()); } /** @@ -84,7 +92,8 @@ protected function resetSessions() */ protected function getSession() { - return self::$mink->getSession('sess'); + assert(self::$mink !== null); + return self::$mink->getSession(self::MINK_SESSION_KEY); } /** @@ -94,7 +103,8 @@ protected function getSession() */ protected function getAssertSession() { - return self::$mink->assertSession('sess'); + assert(self::$mink !== null); + return self::$mink->assertSession(self::MINK_SESSION_KEY); } /** @@ -145,13 +155,13 @@ protected function mapRemoteFilePath($file) */ protected function pathTo($path) { - return rtrim(self::getConfig()->getWebFixturesUrl(), '/').'/'.ltrim($path, '/'); + return rtrim(self::getConfig()->getWebFixturesUrl(), '/') . '/' . ltrim($path, '/'); } /** * Waits for a condition to be true, considering than it is successful for drivers not supporting wait(). * - * @param int $time + * @param int $time * @param string $condition A JS condition to evaluate * * @return bool