From acff1ba9583c3f7928ad092e1d9d0a68355799ba Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sun, 9 May 2021 12:32:25 +0300 Subject: [PATCH] Rename WebView::setJsVar() to WebView::setJsVars() and change syntax for set JS vars and strings (#145) --- src/WebView.php | 133 +++++++++++++++++++++++++++++++++++++----- tests/WebViewTest.php | 78 +++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 13 deletions(-) diff --git a/src/WebView.php b/src/WebView.php index 7f6f60620..a43f43f03 100644 --- a/src/WebView.php +++ b/src/WebView.php @@ -4,6 +4,7 @@ namespace Yiisoft\View; +use InvalidArgumentException; use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Html\Html; use Yiisoft\Html\Tag\Script; @@ -11,6 +12,14 @@ use Yiisoft\View\Event\BodyEnd; use Yiisoft\View\Event\PageEnd; +use function array_key_exists; +use function get_class; +use function gettype; +use function in_array; +use function is_array; +use function is_object; +use function is_string; + /** * View represents a view object in the MVC pattern. * @@ -63,6 +72,9 @@ class WebView extends View */ public const POSITION_LOAD = 5; + private const DEFAULT_POSITION_JS_VARIABLE = self::POSITION_HEAD; + private const DEFAULT_POSITION_JS_STRING = self::POSITION_END; + /** * This is internally used as the placeholder for receiving the content registered for the head section. */ @@ -328,7 +340,7 @@ public function registerCssFile(string $url, array $options = [], string $key = * @param string $key the key that identifies the JS code block. If null, it will use $js as the key. If two JS code * blocks are registered with the same key, the latter will overwrite the former. */ - public function registerJs(string $js, int $position = self::POSITION_END, string $key = null): void + public function registerJs(string $js, int $position = self::POSITION_END, ?string $key = null): void { $key = $key ?: md5($js); $this->js[$position][$key] = $js; @@ -557,13 +569,16 @@ public function setJsFiles(array $jsFiles): void * It processes the JS strings generated by the asset manager. * * @param array $jsStrings + * + * @throws InvalidArgumentException */ public function setJsStrings(array $jsStrings): void { - foreach ($jsStrings as $value) { - $this->registerJs( - $value['string'], - $value['attributes']['position'] + /** @var mixed $value */ + foreach ($jsStrings as $key => $value) { + $this->registerJsStringByConfig( + is_string($key) ? $key : null, + is_array($value) ? $value : [$value, self::DEFAULT_POSITION_JS_STRING] ); } } @@ -571,16 +586,18 @@ public function setJsStrings(array $jsStrings): void /** * It processes the JS variables generated by the asset manager and converts it into JS code. * - * @param array $jsVar + * @param array $jsVars + * + * @throws InvalidArgumentException */ - public function setJsVar(array $jsVar): void + public function setJsVars(array $jsVars): void { - foreach ($jsVar as $key => $value) { - $this->registerJsVar( - (string)$key, - $value['variables'], - $value['attributes']['position'] - ); + foreach ($jsVars as $key => $value) { + if (is_string($key)) { + $this->registerJsVar($key, $value, self::DEFAULT_POSITION_JS_VARIABLE); + } else { + $this->registerJsVarByConfig($value); + } } } @@ -596,6 +613,68 @@ public function setTitle(string $value): void $this->title = $value; } + /** + * @throws InvalidArgumentException + */ + private function registerJsStringByConfig(?string $key, array $config): void + { + if (!array_key_exists(0, $config)) { + throw new InvalidArgumentException('Do not set JS string.'); + } + $js = $config[0]; + + if (!is_string($js) && !($js instanceof Script)) { + throw new InvalidArgumentException( + sprintf( + 'JS string should be string or instance of \\' . Script::class . '. Got %s.', + $this->getType($js), + ) + ); + } + + $position = $config[1] ?? self::DEFAULT_POSITION_JS_STRING; + if (!$this->isValidPosition($position)) { + throw new InvalidArgumentException('Invalid position of JS strings.'); + } + + is_string($js) + ? $this->registerJs($js, $position, $key) + : $this->registerScriptTag($js, $position, $key); + } + + /** + * @throws InvalidArgumentException + */ + private function registerJsVarByConfig(array $config): void + { + if (!array_key_exists(0, $config)) { + throw new InvalidArgumentException('Do not set JS variable name.'); + } + $key = $config[0]; + + if (!is_string($key)) { + throw new InvalidArgumentException( + sprintf( + 'JS variable name should be string. Got %s.', + $this->getType($key), + ) + ); + } + + if (!array_key_exists(1, $config)) { + throw new InvalidArgumentException('Do not set JS variable value.'); + } + /** @var mixed */ + $value = $config[1]; + + $position = $config[2] ?? self::DEFAULT_POSITION_JS_VARIABLE; + if (!$this->isValidPosition($position)) { + throw new InvalidArgumentException('Invalid position of JS variable.'); + } + + $this->registerJsVar($key, $value, $position); + } + /** * @param Script[]|string[] $items */ @@ -633,4 +712,32 @@ private function generateJsWithoutTag(array $items): string } return implode("\n", $js); } + + /** + * @param mixed $position + * + * @psalm-assert int $position + */ + private function isValidPosition($position): bool + { + return in_array( + $position, + [ + self::POSITION_HEAD, + self::POSITION_BEGIN, + self::POSITION_END, + self::POSITION_READY, + self::POSITION_LOAD, + ], + true, + ); + } + + /** + * @param mixed $value + */ + private function getType($value): string + { + return is_object($value) ? get_class($value) : gettype($value); + } } diff --git a/tests/WebViewTest.php b/tests/WebViewTest.php index 8bfa8ec48..0999438e3 100644 --- a/tests/WebViewTest.php +++ b/tests/WebViewTest.php @@ -4,6 +4,7 @@ namespace Yiisoft\View\Tests; +use InvalidArgumentException; use Yiisoft\Files\FileHelper; use Yiisoft\Html\Html; use Yiisoft\View\WebView; @@ -186,4 +187,81 @@ public function testRegisterJsAndRegisterScriptTagWithAjax(): void $html ); } + + public function testSetJsStrings(): void + { + $this->webView->setJsStrings([ + 'uniqueName' => 'app1.start();', + 'app2.start();', + 'uniqueName2' => ['app3.start();', WebView::POSITION_BEGIN], + ['app4.start();', WebView::POSITION_HEAD], + Html::script('{"@type":"Article"}')->type('application/ld+json'), + ]); + + $html = $this->webView->render('//rawlayout.php', ['content' => '']); + + $expected = '123' . "\n" . + '4'; + + $this->assertEqualsWithoutLE($expected, $html); + } + + public function dataFailSetJsStrings(): array + { + return [ + ['Do not set JS string.', [[]]], + ['Do not set JS string.', ['key' => []]], + ['JS string should be string or instance of \Yiisoft\Html\Tag\Script. Got integer.', [[42]]], + ['JS string should be string or instance of \Yiisoft\Html\Tag\Script. Got integer.', ['key' => [42]]], + ['Invalid position of JS strings.', [['alert(1);', 99]]], + ['Invalid position of JS strings.', ['key' => ['alert(1);', 99]]], + ]; + } + + /** + * @dataProvider dataFailSetJsStrings + */ + public function testFailSetJsStrings(string $message, array $jsStrings): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($message); + $this->webView->setJsStrings($jsStrings); + } + + public function testSetJsVars(): void + { + $this->webView->setJsVars([ + 'var1' => 'value1', + 'var2' => [1, 2], + ['var3', 'value3', WebView::POSITION_END], + ]); + + $html = $this->webView->render('//rawlayout.php', ['content' => '']); + + $expected = '1234'; + + $this->assertEqualsWithoutLE($expected, $html); + } + + public function dataFailSetJsVars(): array + { + return [ + ['Do not set JS variable name.', [[]]], + ['JS variable name should be string. Got integer.', [[42]]], + ['Do not set JS variable value.', [['var']]], + ['Invalid position of JS variable.', [['title', 'hello', 99]]], + ]; + } + + /** + * @dataProvider dataFailSetJsVars + */ + public function testFailSetJsVars(string $message, array $jsVars): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($message); + $this->webView->setJsVars($jsVars); + } }