diff --git a/.github/workflows/php-package-ci.yml b/.github/workflows/php-package-ci.yml index b4b66b7..96f92f0 100644 --- a/.github/workflows/php-package-ci.yml +++ b/.github/workflows/php-package-ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [ 7.0, 7.1 ] + php: [ 7.4, 8.0 ] steps: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 @@ -18,7 +18,6 @@ jobs: - run: make composer - - if: matrix.php == '7.1' - run: make phpstan + - run: make phpstan - run: make run-tests diff --git a/Makefile b/Makefile index 1dd7a7a..c24a2ee 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ composer: composer update --no-interaction --prefer-dist phpstan: - vendor/bin/phpstan analyse -l 5 -c phpstan.neon src/ + vendor/bin/phpstan analyse -l 9 -c phpstan.neon src/ run-tests: - vendor/bin/tester tests + vendor/bin/tester tests -d extension=tokenizer.so diff --git a/composer.json b/composer.json index cb627b5..88cf8ff 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,13 @@ } ], "require": { - "php": "~7.0", - "nette/application": ">= 2.2.10 < 2.3.0" + "php": "~7.4 | ~8.0", + "nette/application": "^2.4" }, "require-dev": { - "phpstan/phpstan": "~0.6.0", - "nette/tester": "~1.7.0", - "mockery/mockery": "~0.9.0" + "phpstan/phpstan": "^1.0", + "nette/tester": "^2.0", + "mockery/mockery": "^1.0" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index 1af7d4b..65c46ea 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,10 +1,6 @@ parameters: ignoreErrors: - - '#Call to an undefined method [a-zA-Z0-9\\_]+::renderAsync\(\)#' - - '#Call to an undefined method [a-zA-Z0-9\\_]+::render\(\)#' fileExtensions: - phpt - autoload_directories: -# - %rootDir%/../../../test - + treatPhpDocTypesAsCertain: false diff --git a/src/UI/AsyncControlLink.php b/src/UI/AsyncControlLink.php index 01b5b06..8310f14 100644 --- a/src/UI/AsyncControlLink.php +++ b/src/UI/AsyncControlLink.php @@ -5,42 +5,53 @@ final class AsyncControlLink { - private static $defaultMessage = 'Load content'; - private static $defaultAttributes = []; + private static string $defaultMessage = 'Load content'; + /** - * @var string + * @var array */ - private $message; + private static array $defaultAttributes = []; + + private string $message; + /** - * @var array + * @var array */ - private $attributes; - + private array $attributes; + /** + * @param string|null $message + * @param array $attributes + */ public function __construct( - string $message = NULL, - array $attributes = NULL - ) { - $this->message = $message === NULL ? self::$defaultMessage : $message; - $this->attributes = $attributes === NULL ? self::$defaultAttributes : $attributes; + ?string $message = null, + ?array $attributes = null + ) + { + $this->message = $message === null ? self::$defaultMessage : $message; + $this->attributes = $attributes === null ? self::$defaultAttributes : $attributes; } - - public static function setDefault(string $message, array $attributes = []) + /** + * @param array $attributes + */ + public static function setDefault(string $message, array $attributes = []): void { self::$defaultMessage = $message; self::$defaultAttributes = $attributes; } - public function getMessage(): string { return $this->message; } - + /** + * @return array + */ public function getAttributes(): array { return $this->attributes; } + } diff --git a/src/UI/AsyncControlTrait.php b/src/UI/AsyncControlTrait.php index 3c6c4f0..b2c8c59 100644 --- a/src/UI/AsyncControlTrait.php +++ b/src/UI/AsyncControlTrait.php @@ -3,12 +3,11 @@ namespace Pd\AsyncControl\UI; use Nette\Application\UI\Control; -use Nette\Application\UI\Presenter; use Nette\Bridges\ApplicationLatte\Template; /** - * @method render + * @method render() */ trait AsyncControlTrait { @@ -19,7 +18,7 @@ trait AsyncControlTrait protected $asyncRenderer; - public function handleAsyncLoad() + public function handleAsyncLoad(): void { if ( ! $this instanceof Control || ! ($presenter = $this->getPresenter(FALSE)) || ! $presenter->isAjax()) { return; @@ -27,43 +26,43 @@ public function handleAsyncLoad() ob_start(function () { }); try { - $this->renderAsync(); + $this->doRender(); } catch (\Throwable $e) { ob_end_clean(); throw $e; - } catch (\Exception $e) { - ob_end_clean(); - throw $e; } $content = ob_get_clean(); $presenter->getPayload()->snippets[$this->getSnippetId('async')] = $content; $presenter->sendPayload(); } - - public function renderAsync(string $linkMessage = NULL, array $linkAttributes = NULL) + /** + * @param array $linkAttributes + * @return void + */ + public function renderAsync(string $linkMessage = NULL, array $linkAttributes = NULL): void { - if ( - $this instanceof Control - && $this->getPresenter()->getParameter('_escaped_fragment_') === NULL - && strpos((string) $this->getPresenter()->getParameter(Presenter::SIGNAL_KEY), sprintf('%s-', $this->getUniqueId())) !== 0 - ) { - $template = $this->createTemplate(); - if ($template instanceof Template) { - $template->add('link', new AsyncControlLink($linkMessage, $linkAttributes)); - } - $template->setFile(__DIR__ . '/templates/asyncLoadLink.latte'); - $template->render(); - } elseif (is_callable($this->asyncRenderer)) { - call_user_func($this->asyncRenderer); - } else { - $this->render(); + $template = $this->createTemplate(); + if ($template instanceof Template) { + $template->add('link', new AsyncControlLink($linkMessage, $linkAttributes)); } + $template->setFile(__DIR__ . '/templates/asyncLoadLink.latte'); + $template->render(); } - public function setAsyncRenderer(callable $renderer) + public function setAsyncRenderer(callable $renderer): void { $this->asyncRenderer = $renderer; } + + + protected function doRender(): void + { + if (is_callable($this->asyncRenderer)) { + call_user_func($this->asyncRenderer); + } else { + $this->render(); + } + } } diff --git a/tests/UI/AsyncControlLinkTest.phpt b/tests/UI/AsyncControlLinkTest.phpt index 4ba35d6..f7ef0bf 100644 --- a/tests/UI/AsyncControlLinkTest.phpt +++ b/tests/UI/AsyncControlLinkTest.phpt @@ -8,11 +8,13 @@ use Tester\TestCase; require_once __DIR__ . '/../../vendor/autoload.php'; - +/** + * @testCase + */ final class AsyncControlLinkTest extends TestCase { - public function testLink() + public function testLink(): void { $link = new AsyncControlLink; Assert::equal('Load content', $link->getMessage()); diff --git a/tests/UI/AsyncControlTest.phpt b/tests/UI/AsyncControlTest.phpt index 076ddf8..0991e4f 100644 --- a/tests/UI/AsyncControlTest.phpt +++ b/tests/UI/AsyncControlTest.phpt @@ -3,8 +3,9 @@ namespace Pd\AsyncControl\UI; use Mockery; -use Nette\Application\UI\ITemplate; -use Nette\Application\UI\ITemplateFactory; + +use Nette\Bridges\ApplicationLatte\TemplateFactory; +use Nette\Bridges\ApplicationLatte\Template; use Nette\Application\UI\Presenter; use Tester\Assert; use Tester\TestCase; @@ -12,27 +13,26 @@ use Tester\TestCase; require_once __DIR__ . '/../../vendor/autoload.php'; +\Tester\Environment::bypassFinals(); +/** + * @testCase + */ final class AsyncControlTest extends TestCase { - const VALID_SIGNAL = 'control-form-submit'; - const FRAGMENT_PARAMETER = '_escaped_fragment_'; - - public function testHandleAjax() + public function testHandleAjax(): void { $presenter = Mockery::mock(Presenter::class); $presenter->shouldReceive('isAjax')->once()->andReturn(TRUE); - $presenter->shouldReceive('getPayload')->andReturn($payload = new \stdClass); + $presenter->shouldReceive('getPayload')->andReturn($payload = new \stdClass()); $presenter->shouldReceive('sendPayload')->once(); - /** - * @var AsyncControl|Mockery\Mock $control - */ - $control = Mockery::mock(AsyncControl::class)->makePartial(); + + $control = Mockery::mock(AsyncControl::class)->makePartial()->shouldAllowMockingProtectedMethods(); $control->shouldReceive('getPresenter')->andReturn($presenter); $renderedContent = 'rendered content'; - $control->shouldReceive('renderAsync')->once()->andReturnUsing(function () use ($renderedContent) { + $control->shouldReceive('doRender')->once()->andReturnUsing(function () use ($renderedContent) { echo $renderedContent; }) ; @@ -43,15 +43,13 @@ final class AsyncControlTest extends TestCase } - public function testHandleNoAjax() + public function testHandleNoAjax(): void { $presenter = Mockery::mock(Presenter::class); $presenter->shouldReceive('isAjax')->once()->andReturn(FALSE); $presenter->shouldNotReceive('getPayload'); $presenter->shouldNotReceive('sendPayload'); - /** - * @var AsyncControl|Mockery\Mock $control - */ + $control = Mockery::mock(AsyncControl::class)->makePartial(); $control->shouldReceive('getPresenter')->andReturn($presenter); $control->shouldNotReceive('renderAsync'); @@ -60,82 +58,39 @@ final class AsyncControlTest extends TestCase } - public function testRenderAsyncLoadLink() + public function testRenderAsyncLoadsLink(): void { - /** - * @var AsyncControl|Mockery\Mock $control - */ $control = Mockery::mock(AsyncControl::class)->makePartial(); - $template = Mockery::mock(ITemplate::class); + $template = Mockery::mock(Template::class); + $template->shouldReceive('add')->once()->with('link', Mockery::type(AsyncControlLink::class)); $template->shouldReceive('setFile')->once()->withAnyArgs(); $template->shouldReceive('render')->once(); - $templateFactory = Mockery::mock(ITemplateFactory::class); + $templateFactory = Mockery::mock(TemplateFactory::class); $templateFactory->shouldReceive('createTemplate')->once()->with($control)->andReturn($template); $presenter = Mockery::mock(Presenter::class); - $presenter->shouldReceive('getParameter')->once()->with(self::FRAGMENT_PARAMETER)->andReturn(NULL); - $presenter->shouldReceive('getParameter')->once()->with(Presenter::SIGNAL_KEY)->andReturn(NULL); $presenter->shouldReceive('getTemplateFactory')->once()->andReturn($templateFactory); - $control->shouldReceive('getPresenter')->andReturn($presenter); - $control->shouldReceive('getUniqueId')->once()->andReturn('control'); - $control->renderAsync(); - } - - - public function testRenderWithSignal() - { - $presenter = Mockery::mock(Presenter::class); - $presenter->shouldReceive('getParameter')->once()->with(self::FRAGMENT_PARAMETER)->andReturn(NULL); - $presenter->shouldReceive('getParameter')->once()->with(Presenter::SIGNAL_KEY)->andReturn(self::VALID_SIGNAL); - /** - * @var AsyncControl|Mockery\Mock $control - */ - $control = Mockery::mock(AsyncControl::class)->makePartial(); - $control->shouldReceive('getPresenter')->andReturn($presenter); - $control->shouldReceive('getUniqueId')->once()->andReturn('control'); - $control->shouldReceive('render')->once(); - $control->renderAsync(); - } - - - public function testRenderWithFragment() - { - $presenter = Mockery::mock(Presenter::class); - $presenter->shouldReceive('getParameter')->once()->with(self::FRAGMENT_PARAMETER)->andReturn(''); - /** - * @var AsyncControl|Mockery\Mock $control - */ - $control = Mockery::mock(AsyncControl::class)->makePartial(); - $control->shouldReceive('getPresenter')->andReturn($presenter); - $control->shouldReceive('render')->once(); + $control->shouldReceive('getPresenter')->once()->andReturn($presenter); $control->renderAsync(); } - public function testRenderAsyncRenderer() + public function testRenderAsyncRenderer(): void { - $presenter = Mockery::mock(Presenter::class); - $presenter->shouldReceive('getParameter')->once()->with(self::FRAGMENT_PARAMETER)->andReturn(NULL); - $presenter->shouldReceive('getParameter')->once()->with(Presenter::SIGNAL_KEY)->andReturn(self::VALID_SIGNAL); - /** - * @var AsyncControl|Mockery\Mock $control - */ - $control = Mockery::mock(AsyncControl::class)->makePartial(); - $control->shouldReceive('getPresenter')->andReturn($presenter); - $control->shouldReceive('getUniqueId')->once()->andReturn('control'); + $control = Mockery::mock(AsyncControl::class)->makePartial()->shouldAllowMockingProtectedMethods(); $asyncRendered = FALSE; $control->setAsyncRenderer(function () use (&$asyncRendered) { $asyncRendered = TRUE; }); - $control->renderAsync(); + $control->doRender(); Assert::equal(TRUE, $asyncRendered); } - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); Mockery::close();