diff --git a/AbstractBrowser.php b/AbstractBrowser.php index 1269fcb..bcb3b4b 100644 --- a/AbstractBrowser.php +++ b/AbstractBrowser.php @@ -19,6 +19,7 @@ use Symfony\Component\DomCrawler\Form; use Symfony\Component\DomCrawler\Link; use Symfony\Component\Process\PhpProcess; +use Symfony\Component\Process\Process; /** * Simulates a browser. @@ -45,6 +46,7 @@ abstract class AbstractBrowser /** @psalm-var TResponse */ protected object $response; protected Crawler $crawler; + /** @deprecated since Symfony 7.4, to be removed in Symfony 8 */ protected bool $useHtml5Parser = true; protected bool $insulated = false; protected ?string $redirect; @@ -114,7 +116,7 @@ public function getMaxRedirects(): int */ public function insulate(bool $insulated = true): void { - if ($insulated && !class_exists(\Symfony\Component\Process\Process::class)) { + if ($insulated && !class_exists(Process::class)) { throw new LogicException('Unable to isolate requests as the Symfony Process Component is not installed. Try running "composer require symfony/process".'); } @@ -203,10 +205,16 @@ public function getCrawler(): Crawler /** * Sets whether parsing should be done using "masterminds/html5". * + * @deprecated since Symfony 7.4, Symfony 8 will unconditionally use the native HTML5 parser + * * @return $this */ public function useHtml5Parser(bool $useHtml5Parser): static { + if (\PHP_VERSION_ID >= 80400) { + trigger_deprecation('symfony/browser-kit', '7.4', 'Method "%s()" is deprecated. Symfony 8 will unconditionally use the native HTML5 parser.', __METHOD__); + } + $this->useHtml5Parser = $useHtml5Parser; return $this; @@ -461,10 +469,10 @@ abstract protected function doRequest(object $request); /** * Returns the script to execute when the request must be insulated. * - * @psalm-param TRequest $request - * * @param object $request An origin request instance * + * @psalm-param TRequest $request + * * @return string * * @throws LogicException When this abstract class is not implemented @@ -567,7 +575,7 @@ public function followRedirect(): Crawler $request = $this->internalRequest; - if (\in_array($this->internalResponse->getStatusCode(), [301, 302, 303])) { + if (\in_array($this->internalResponse->getStatusCode(), [301, 302, 303], true)) { $method = 'GET'; $files = []; $content = null; diff --git a/CHANGELOG.md b/CHANGELOG.md index b05e307..ad57c95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +7.4 +--- + + * Add `isFirstPage()` and `isLastPage()` methods to the History class for checking navigation boundaries + * Add PHPUnit constraints: `BrowserHistoryIsOnFirstPage` and `BrowserHistoryIsOnLastPage` + * Deprecate `AbstractBrowser::useHtml5Parser()`; Symfony 8 will unconditionally use the native HTML5 parser + 6.4 --- diff --git a/History.php b/History.php index 8fe4f2b..5926b40 100644 --- a/History.php +++ b/History.php @@ -50,6 +50,22 @@ public function isEmpty(): bool return 0 === \count($this->stack); } + /** + * Returns true if the stack is on the first page. + */ + public function isFirstPage(): bool + { + return $this->position < 1; + } + + /** + * Returns true if the stack is on the last page. + */ + public function isLastPage(): bool + { + return $this->position > \count($this->stack) - 2; + } + /** * Goes back in the history. * @@ -57,7 +73,7 @@ public function isEmpty(): bool */ public function back(): Request { - if ($this->position < 1) { + if ($this->isFirstPage()) { throw new LogicException('You are already on the first page.'); } @@ -71,7 +87,7 @@ public function back(): Request */ public function forward(): Request { - if ($this->position > \count($this->stack) - 2) { + if ($this->isLastPage()) { throw new LogicException('You are already on the last page.'); } diff --git a/HttpBrowser.php b/HttpBrowser.php index 4f04442..c7ae5f6 100644 --- a/HttpBrowser.php +++ b/HttpBrowser.php @@ -64,7 +64,7 @@ protected function doRequest(object $request): Response */ private function getBodyAndExtraHeaders(Request $request, array $headers): array { - if (\in_array($request->getMethod(), ['GET', 'HEAD']) && !isset($headers['content-type'])) { + if (\in_array($request->getMethod(), ['GET', 'HEAD'], true) && !isset($headers['content-type'])) { return ['', []]; } diff --git a/Test/Constraint/BrowserHistoryIsOnFirstPage.php b/Test/Constraint/BrowserHistoryIsOnFirstPage.php new file mode 100644 index 0000000..be5be9a --- /dev/null +++ b/Test/Constraint/BrowserHistoryIsOnFirstPage.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\BrowserKit\AbstractBrowser; + +final class BrowserHistoryIsOnFirstPage extends Constraint +{ + public function toString(): string + { + return 'is on the first page'; + } + + protected function matches($other): bool + { + if (!$other instanceof AbstractBrowser) { + throw new \LogicException('Can only test on an AbstractBrowser instance.'); + } + + return $other->getHistory()->isFirstPage(); + } + + protected function failureDescription($other): string + { + return 'the Browser history '.$this->toString(); + } +} diff --git a/Test/Constraint/BrowserHistoryIsOnLastPage.php b/Test/Constraint/BrowserHistoryIsOnLastPage.php new file mode 100644 index 0000000..38658a0 --- /dev/null +++ b/Test/Constraint/BrowserHistoryIsOnLastPage.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\BrowserKit\AbstractBrowser; + +final class BrowserHistoryIsOnLastPage extends Constraint +{ + public function toString(): string + { + return 'is on the last page'; + } + + protected function matches($other): bool + { + if (!$other instanceof AbstractBrowser) { + throw new \LogicException('Can only test on an AbstractBrowser instance.'); + } + + return $other->getHistory()->isLastPage(); + } + + protected function failureDescription($other): string + { + return 'the Browser history '.$this->toString(); + } +} diff --git a/Tests/AbstractBrowserTest.php b/Tests/AbstractBrowserTest.php index dd7f8e4..87290de 100644 --- a/Tests/AbstractBrowserTest.php +++ b/Tests/AbstractBrowserTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\BrowserKit\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use PHPUnit\Framework\TestCase; use Symfony\Component\BrowserKit\CookieJar; use Symfony\Component\BrowserKit\Exception\BadMethodCallException; @@ -650,9 +652,7 @@ public function testFollowRedirectDropPostMethod() } } - /** - * @dataProvider getTestsForMetaRefresh - */ + #[DataProvider('getTestsForMetaRefresh')] public function testFollowMetaRefresh(string $content, string $expectedEndingUrl, bool $followMetaRefresh = true) { $client = $this->getBrowser(); @@ -701,6 +701,8 @@ public function testBack() $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->back() keeps files'); $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->back() keeps $_SERVER'); $this->assertSame($content, $client->getRequest()->getContent(), '->back() keeps content'); + $this->assertTrue($client->getHistory()->isFirstPage()); + $this->assertFalse($client->getHistory()->isLastPage()); } public function testForward() @@ -741,6 +743,8 @@ public function testBackAndFrowardWithRedirects() $client->forward(); $this->assertSame('http://www.example.com/redirected', $client->getRequest()->getUri(), '->forward() goes forward in the history skipping redirects'); + $this->assertTrue($client->getHistory()->isLastPage()); + $this->assertFalse($client->getHistory()->isFirstPage()); } public function testReload() @@ -772,9 +776,7 @@ public function testRestart() $this->assertSame([], $client->getCookieJar()->all(), '->restart() clears the cookies'); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testInsulatedRequests() { $client = $this->getBrowser(); diff --git a/Tests/CookieJarTest.php b/Tests/CookieJarTest.php index 2e456b8..fc2a6f3 100644 --- a/Tests/CookieJarTest.php +++ b/Tests/CookieJarTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\BrowserKit\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\CookieJar; @@ -114,9 +115,7 @@ public function testUpdateFromSetCookieWithMultipleCookies() $this->assertEquals($timestamp, $phpCookie->getExpiresTime()); } - /** - * @dataProvider provideAllValuesValues - */ + #[DataProvider('provideAllValuesValues')] public function testAllValues($uri, $values) { $cookieJar = new CookieJar(); diff --git a/Tests/CookieTest.php b/Tests/CookieTest.php index 5d42ceb..a8a6f52 100644 --- a/Tests/CookieTest.php +++ b/Tests/CookieTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\BrowserKit\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\Exception\InvalidArgumentException; @@ -36,9 +37,7 @@ public function testToString() $this->assertEquals('foo=bar; expires=Thu, 01 Jan 1970 00:00:02 GMT; path=/; secure; httponly; samesite=lax', (string) $cookie); } - /** - * @dataProvider getTestsForToFromString - */ + #[DataProvider('getTestsForToFromString')] public function testToFromString($cookie, $url = null) { $this->assertEquals($cookie, (string) Cookie::fromString($cookie, $url)); @@ -65,9 +64,7 @@ public function testFromStringIgnoreSecureFlag() $this->assertFalse(Cookie::fromString('foo=bar; secure', 'http://example.com/')->isSecure()); } - /** - * @dataProvider getExpireCookieStrings - */ + #[DataProvider('getExpireCookieStrings')] public function testFromStringAcceptsSeveralExpiresDateFormats($cookie) { $this->assertEquals(1596185377, Cookie::fromString($cookie)->getExpiresTime()); diff --git a/Tests/HistoryTest.php b/Tests/HistoryTest.php index 8f3cfae..1e1acb2 100644 --- a/Tests/HistoryTest.php +++ b/Tests/HistoryTest.php @@ -99,4 +99,34 @@ public function testForward() $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->forward() returns the next request in the history'); } + + public function testIsFirstPage() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + $history->add(new Request('http://www.example1.com/', 'get')); + $history->back(); + + $this->assertFalse($history->isLastPage()); + $this->assertTrue($history->isFirstPage()); + } + + public function testIsLastPage() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + $history->add(new Request('http://www.example1.com/', 'get')); + + $this->assertTrue($history->isLastPage()); + $this->assertFalse($history->isFirstPage()); + } + + public function testIsFirstAndLastPage() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertTrue($history->isLastPage()); + $this->assertTrue($history->isFirstPage()); + } } diff --git a/Tests/HttpBrowserTest.php b/Tests/HttpBrowserTest.php index 3a2547d..322f1c5 100644 --- a/Tests/HttpBrowserTest.php +++ b/Tests/HttpBrowserTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\BrowserKit\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\BrowserKit\CookieJar; use Symfony\Component\BrowserKit\History; use Symfony\Component\BrowserKit\HttpBrowser; @@ -26,9 +27,7 @@ public function getBrowser(array $server = [], ?History $history = null, ?Cookie return new TestHttpClient($server, $history, $cookieJar); } - /** - * @dataProvider validContentTypes - */ + #[DataProvider('validContentTypes')] public function testRequestHeaders(array $requestArguments, array $expectedArguments) { $client = $this->createMock(HttpClientInterface::class); @@ -186,9 +185,7 @@ public function testMultiPartRequestWithAdditionalParametersOfTheSameName() ]); } - /** - * @dataProvider forwardSlashesRequestPathProvider - */ + #[DataProvider('forwardSlashesRequestPathProvider')] public function testMultipleForwardSlashesRequestPath(string $requestPath) { $client = $this->createMock(HttpClientInterface::class); diff --git a/Tests/ResponseTest.php b/Tests/ResponseTest.php index f68d0b5..37b4b46 100644 --- a/Tests/ResponseTest.php +++ b/Tests/ResponseTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\BrowserKit\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\BrowserKit\Exception\JsonException; use Symfony\Component\BrowserKit\Response; @@ -90,9 +91,7 @@ public function testToArray() ], $response->toArray(), '->toArray returns an array representation of json content'); } - /** - * @dataProvider provideInvalidJson - */ + #[DataProvider('provideInvalidJson')] public function testToArrayThrowsErrorOnInvalidJson(string $data) { $response = new Response($data); diff --git a/Tests/TestClient.php b/Tests/TestClient.php index dc27e3b..b70e002 100644 --- a/Tests/TestClient.php +++ b/Tests/TestClient.php @@ -47,11 +47,11 @@ protected function getScript(object $request): string $path = $r->getFileName(); return <<nextScript); -EOF; + echo serialize($this->nextScript); + EOF; } } diff --git a/Tests/TestHttpClient.php b/Tests/TestHttpClient.php index 3d0a354..9de38eb 100644 --- a/Tests/TestHttpClient.php +++ b/Tests/TestHttpClient.php @@ -70,11 +70,11 @@ protected function getScript(object $request): string $path = $r->getFileName(); return <<nextScript); -EOF; + echo serialize($this->nextScript); + EOF; } } diff --git a/composer.json b/composer.json index e145984..a123577 100644 --- a/composer.json +++ b/composer.json @@ -17,13 +17,14 @@ ], "require": { "php": ">=8.2", - "symfony/dom-crawler": "^6.4|^7.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/dom-crawler": "^6.4|^7.0|^8.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0" + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "autoload": { "psr-4": { "Symfony\\Component\\BrowserKit\\": "" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 747ed25..2272dfe 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ @@ -18,7 +19,7 @@ - + ./ @@ -27,5 +28,9 @@ ./Tests ./vendor - + + + + +