From 30e07393198d7dbdee22435b9450af0d375ac051 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Mon, 20 Nov 2023 14:09:19 -0300 Subject: [PATCH] Iniitial commit. --- .github/workflows/build.yml | 4 +- .github/workflows/compatibility.yml | 34 +++ .github/workflows/dependency-check.yml | 4 +- .github/workflows/mutation.yml | 33 +++ .github/workflows/static.yml | 2 + README.md | 101 ++++++-- composer.json | 34 ++- phpunit.xml.dist | 4 +- psalm.xml | 20 ++ src/Asset/SelectizeAsset.php | 39 +++ src/Example.php | 13 - src/RegisterClientScript.php | 36 +++ src/Selectize.php | 52 ++++ tests/AssetTest.php | 60 +++++ tests/ExampleTest.php | 18 -- tests/ExceptionTest.php | 26 ++ tests/SelectizeTest.php | 319 +++++++++++++++++++++++++ tests/Support/SelectizeModel.php | 10 + tests/Support/bootstrap.php | 21 ++ tests/Support/main.php | 15 ++ tests/Support/runtime/.gitkeep | 0 tests/TestCase.php | 62 +++++ 22 files changed, 843 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/compatibility.yml create mode 100644 .github/workflows/mutation.yml create mode 100644 psalm.xml create mode 100644 src/Asset/SelectizeAsset.php delete mode 100644 src/Example.php create mode 100644 src/RegisterClientScript.php create mode 100644 src/Selectize.php create mode 100644 tests/AssetTest.php delete mode 100644 tests/ExampleTest.php create mode 100644 tests/ExceptionTest.php create mode 100644 tests/SelectizeTest.php create mode 100644 tests/Support/SelectizeModel.php create mode 100644 tests/Support/bootstrap.php create mode 100644 tests/Support/main.php create mode 100644 tests/Support/runtime/.gitkeep create mode 100644 tests/TestCase.php diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 719d5a9..8e18eee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,8 +23,10 @@ name: build jobs: phpunit: - uses: yiisoft/actions/.github/workflows/phpunit.yml@master + uses: php-forge/actions/.github/workflows/phpunit.yml@main with: + composer-command: | + composer require yiisoft/yii2:^2.2.x-dev --prefer-dist --no-progress --no-interaction --no-scripts --ansi os: >- ['ubuntu-latest', 'windows-latest'] php: >- diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml new file mode 100644 index 0000000..438ac54 --- /dev/null +++ b/.github/workflows/compatibility.yml @@ -0,0 +1,34 @@ +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + +name: compatibility + +jobs: + phpunit: + uses: php-forge/actions/.github/workflows/phpunit.yml@main + with: + composer-command: | + composer require yiisoft/yii2:^2.0.49 --prefer-dist --no-progress --no-interaction --no-scripts --ansi + extensions: intl + os: >- + ['ubuntu-latest', 'windows-latest'] + php: >- + ['8.1', '8.2', '8.3'] diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index b115ab1..6ad212e 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -25,8 +25,10 @@ name: Composer require checker jobs: composer-require-checker: - uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@master + uses: php-forge/actions/.github/workflows/composer-require-checker.yml@main with: + composer-command: | + composer require yiisoft/yii2:^2.2.x-dev --prefer-dist --no-progress --no-interaction --no-scripts --ansi os: >- ['ubuntu-latest'] php: >- diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..e807dd1 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,33 @@ +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'psalm.xml' + + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'psalm.xml' + +name: mutation test + +jobs: + mutation: + uses: php-forge/actions/.github/workflows/roave-infection.yml@main + secrets: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + with: + composer-command: | + composer require yiisoft/yii2:^2.2.x-dev --prefer-dist --no-progress --no-interaction --no-scripts --ansi + os: >- + ['ubuntu-latest'] + php: >- + ['8.1'] diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 75a378c..c02ceb2 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -25,6 +25,8 @@ jobs: phpstan: uses: php-forge/actions/.github/workflows/phpstan.yml@main with: + composer-command: | + composer require yiisoft/yii2:^2.2.x-dev --prefer-dist --no-progress --no-interaction --no-scripts --ansi os: >- ['ubuntu-latest'] php: >- diff --git a/README.md b/README.md index 7fc0a59..27ed12b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

- + -

Yii2-Template.

+

Selectize.


@@ -10,35 +10,92 @@ php-version - - yii2-version + + PHPUnit - - PHPUnit - - - Codecov - - - PHPStan - - - PHPStan level - - - Code style + + PHPUnit + + Codecov + + + Infection +

-## Requirements +## Installation + +The preferred way to install this extension is through [composer](https://getcomposer.org/download/). -The minimun version of `PHP` required by this package is `PHP 8.1`. +Either run -For install this package, you need [composer](https://getcomposer.org/). +``` +composer require --dev --prefer-dist yii2-extensions/selectize +``` + +or add + +``` +"yii2-extensions/selectize": "dev-main" +``` + +to the require-dev section of your `composer.json` file. ## Usage -[Check the documentation docs](/docs/README.md) to learn about usage. +### DropdownList + +```php +use Yii2\Extension\Selectize\Selectize; + +Selectize::widget( + [ + 'attribute' => 'tags', + 'items' => ['foo', 'bar'], + 'model' => new SelectizeModel(), // your model + ], +); +``` + +### Text input + +```php +use Yii2\Extension\Selectize\Selectize; + +Selectize::widget( + [ + 'attribute' => 'tags', + 'items' => ['foo', 'bar'], + 'model' => new SelectizeModel(), // your model + 'type' => Selectize::TYPE_TEXT, // `Selectize::TYPE_SELECT`, `Selectize::TYPE_TEXT` + ], +); +``` + +### Properties of the widget + +| Property | Type | Description | Default | +|----------------|---------------|----------------------------------------------------------------------------------|--------------------------| +| `attribute` | `string` | The attribute associated with this widget. | `null` | +| `clientOptions`| `array` | The options for the underlying Selectize JS plugin. | `[]` | +| `items` | `array` | Items to be displayed in the dropdown list. | `[]` | +| `loadUrl` | `string` | The URL that will return JSON data. | `null` | +| `model` | `Model` | The data model that this widget is associated with. | `null` | +| `options` | `array` | The HTML attributes for the widget container tag. | `[]` | +| `queryParam` | `string` | The name of the parameter that will be sent to the server with the search query. | `query` | +| `type` | `string` | The type of the widget. | `Selectize::TYPE_SELECT` | + +## Quality code + +[![static-analysis](https://github.com/yii2-extensions/selectize/actions/workflows/static.yml/badge.svg)](https://github.com/yii2-extensions/selectize/actions/workflows/static.yml) +[![phpstan-level](https://img.shields.io/badge/PHPStan%20level-5-blue)](https://github.com/yii2-extensions/selectize/actions/workflows/static.yml) +[![StyleCI](https://github.styleci.io/repos/720718108/shield?branch=main)](https://github.styleci.io/repos/720718108?branch=main) + +## Support versions Yii2 + +[![Yii20](https://img.shields.io/badge/Yii2%20version-2.0-blue)](https://github.com/yiisoft/yii2/tree/2.0.49.3) +[![Yii22](https://img.shields.io/badge/Yii2%20version-2.2-blue)](https://github.com/yiisoft/yii2/tree/2.2) ## Testing diff --git a/composer.json b/composer.json index cd5155d..016c3dd 100644 --- a/composer.json +++ b/composer.json @@ -1,41 +1,61 @@ { - "name": "yii2/template", + "name": "yii2-extension/selectize", "type": "library", - "description": "_____", + "description": "Selectize widget for Yii Framework 2", "keywords": [ - "_____" + "yii2", + "selectize", + "widget" ], "license": "mit", "minimum-stability": "dev", "prefer-stable": true, "require": { "php": ">=8.1", - "yiisoft/yii2": "^2.2" + "npm-asset/selectize--selectize": "^0.15.2", + "oomphinc/composer-installers-extender": "^2.0", + "yii2-extensions/asset-bootstrap5": "dev-main", + "yiisoft/yii2": "*" }, "require-dev": { "maglnet/composer-require-checker": "^4.6", + "php-forge/support": "dev-main", "phpunit/phpunit": "^10.2", + "roave/infection-static-analysis-plugin": "^1.32", "yii2-extensions/phpstan": "dev-main" }, "autoload": { "psr-4": { - "yii\\template\\": "src" + "Yii2\\Extensions\\Selectize\\": "src" } }, "autoload-dev": { "psr-4": { - "yii\\template\\tests\\": "tests" + "Yii2\\Extensions\\Selectize\\Tests\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "1.0.x-dev" + }, + "installer-types": [ + "bower-asset", + "npm-asset" + ], + "installer-paths": { + "./node_modules/{$name}/": [ + "type:bower-asset", + "type:npm-asset" + ] } }, "config": { "sort-packages": true, "allow-plugins": { - "yiisoft/yii2-composer": true + "yiisoft/yii2-composer": true, + "composer/installers": true, + "oomphinc/composer-installers-extender": true, + "infection/extension-installer": true } }, "scripts": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f29a28d..fbe7d96 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ - + tests diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..cefdd03 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/src/Asset/SelectizeAsset.php b/src/Asset/SelectizeAsset.php new file mode 100644 index 0000000..d8fac3c --- /dev/null +++ b/src/Asset/SelectizeAsset.php @@ -0,0 +1,39 @@ +options['id']; + + if ($this->loadUrl !== '') { + $url = Url::to($this->loadUrl); + $this->clientOptions['load'] = new JsExpression( + "function (query, callback) { if (!query.length) return callback(); $.getJSON('$url', { {$this->queryParam}: query }, function (data) { callback(data); }).fail(function () { callback(); }); }" + ); + } + + $options = Json::encode($this->clientOptions); + $view = $this->getView(); + + SelectizeAsset::register($view); + + $view->registerJs("jQuery('#$id').selectize($options);"); + } +} diff --git a/src/Selectize.php b/src/Selectize.php new file mode 100644 index 0000000..e855c97 --- /dev/null +++ b/src/Selectize.php @@ -0,0 +1,52 @@ +type) { + static::TYPE_SELECT => $this->generateDropdownList(), + static::TYPE_TEXT => $this->generateTextInput(), + default => throw new InvalidArgumentException('Invalid type "' . $this->type . '".'), + }; + + $this->registerClientScript(); + + return $html; + } + + private function generateTextInput(): string + { + return match ($this->hasModel()) { + true => Html::activeTextInput($this->model, $this->attribute, $this->options), + default => Html::textInput($this->name, $this->value, $this->options), + }; + } + + private function generateDropdownList(): string + { + return match ($this->hasModel()) { + true => Html::activeDropDownList($this->model, $this->attribute, $this->items, $this->options), + default => Html::dropDownList($this->name, $this->value, $this->items, $this->options), + }; + } +} diff --git a/tests/AssetTest.php b/tests/AssetTest.php new file mode 100644 index 0000000..be2868a --- /dev/null +++ b/tests/AssetTest.php @@ -0,0 +1,60 @@ +mockApplication(); + + $view = Yii::$app->getView(); + + $this->assertEmpty($view->assetBundles); + + SelectizeAsset::register($view); + + $this->assertCount(4, $view->assetBundles); + + $this->assertArrayHasKey(SelectizeAsset::class, $view->assetBundles); + $this->assertArrayHasKey(BootstrapAsset::class, $view->assetBundles); + $this->assertArrayHasKey(BootstrapPluginAsset::class, $view->assetBundles); + $this->assertArrayHasKey(JqueryAsset::class, $view->assetBundles); + } + + public function testFileSelectizeAssetRegister(): void + { + $this->mockApplication(); + + $view = new View(); + + $this->assertEmpty($view->assetBundles); + + SelectizeAsset::register($view); + + $this->assertCount(4, $view->assetBundles); + $this->assertInstanceOf(AssetBundle::class, $view->assetBundles[SelectizeAsset::class]); + + $result = $view->renderFile( + __DIR__ . '/Support/main.php', + ['widget' => Selectize::widget(['name' => 'tags', 'id' => 'tests-id'])], + ); + + $this->assertStringContainsString('bootstrap.css', $result); + $this->assertStringContainsString('selectize.bootstrap5.css', $result); + $this->assertStringContainsString('bootstrap.bundle.js', $result); + $this->assertStringContainsString('jquery.js', $result); + $this->assertStringContainsString('selectize.js', $result); + } +} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 2825a4e..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue($example->getExample()); - } -} diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php new file mode 100644 index 0000000..801f2c0 --- /dev/null +++ b/tests/ExceptionTest.php @@ -0,0 +1,26 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type "not-exist".'); + + Selectize::widget( + [ + 'attribute' => 'content', + 'model' => new SelectizeModel(), + 'type' => 'not-exist', + ], + ); + } +} diff --git a/tests/SelectizeTest.php b/tests/SelectizeTest.php new file mode 100644 index 0000000..e8cfe6f --- /dev/null +++ b/tests/SelectizeTest.php @@ -0,0 +1,319 @@ +mockApplication(); + + $this->view = Yii::$app->getView(); + } + + public function testId(): void + { + $selectize = Selectize::widget( + [ + 'name' => 'tags', + 'id' => 'tests-id', + ], + ); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE( + << + + + HTML, + $selectize, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#tests-id').selectize([]); + }); + JS, + $render, + ); + } + + public function testIdWithTypeTextInput(): void + { + $selectize = Selectize::widget( + [ + 'name' => 'tags', + 'id' => 'tests-id', + 'type' => Selectize::TYPE_TEXT, + ], + ); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE('', $selectize); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#tests-id').selectize([]); + }); + JS, + $render, + ); + } + + public function testItems(): void + { + $selectize = Selectize::widget( + [ + 'attribute' => 'tags', + 'items' => ['foo', 'bar'], + 'model' => new SelectizeModel(), + ], + ); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE( + << + + + + HTML, + $selectize, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#selectizemodel-tags').selectize([]); + }); + JS, + $render, + ); + } + + public function testItemsWithTypeTextInput(): void + { + $selectize = Selectize::widget( + [ + 'attribute' => 'tags', + 'items' => ['foo', 'bar'], + 'model' => new SelectizeModel(), + 'type' => Selectize::TYPE_TEXT, + ], + ); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE( + '', + $selectize, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#selectizemodel-tags').selectize([]); + }); + JS, + $render, + ); + } + + public function testLoadUrl(): void + { + $selectizeDropdownList = Selectize::widget( + [ + 'attribute' => 'tags', + 'loadUrl' => '/tags', + 'model' => new SelectizeModel(), + ], + ); + + $render = $this->view->renderFile( + __DIR__ . '/Support/main.php', + [ + 'widget' => $selectizeDropdownList, + ], + ); + + Assert::equalsWithoutLE( + << + + + HTML, + $selectizeDropdownList, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#selectizemodel-tags').selectize({"load":function (query, callback) { if (!query.length) return callback(); $.getJSON('/tags', { query: query }, function (data) { callback(data); }).fail(function () { callback(); }); }}); + }); + JS, + $render, + ); + } + + public function testLoadUrlWithTypeTextInput(): void + { + $selectize = Selectize::widget( + [ + 'attribute' => 'tags', + 'loadUrl' => '/tags', + 'model' => new SelectizeModel(), + 'type' => Selectize::TYPE_TEXT, + ], + ); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE( + '', + $selectize, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#selectizemodel-tags').selectize({"load":function (query, callback) { if (!query.length) return callback(); $.getJSON('/tags', { query: query }, function (data) { callback(data); }).fail(function () { callback(); }); }}); + }); + JS, + $render, + ); + } + + public function testRender(): void + { + $selectizeDropdownList = Selectize::widget( + [ + 'attribute' => 'tags', + 'model' => new SelectizeModel(), + ], + ); + + $render = $this->view->renderFile( + __DIR__ . '/Support/main.php', + [ + 'widget' => $selectizeDropdownList, + ], + ); + + Assert::equalsWithoutLE( + << + + + HTML, + $selectizeDropdownList, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#selectizemodel-tags').selectize([]); + }); + JS, + $render, + ); + } + + public function testRenderWithTypeTextInput(): void + { + $selectize = Selectize::widget( + [ + 'attribute' => 'tags', + 'model' => new SelectizeModel(), + 'type' => Selectize::TYPE_TEXT, + ], + ); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE( + '', + $selectize, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#selectizemodel-tags').selectize([]); + }); + JS, + $render, + ); + } + + public function testWithoutModel(): void + { + $selectizeDropdownList = Selectize::widget(['name' => 'tags']); + + $render = $this->view->renderFile( + __DIR__ . '/Support/main.php', + [ + 'widget' => $selectizeDropdownList, + ], + ); + + Assert::equalsWithoutLE( + << + + + HTML, + $selectizeDropdownList, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#w0').selectize([]); + }); + JS, + $render, + ); + } + + public function testWithoutModelWithTypeTextInput(): void + { + $selectize = Selectize::widget(['name' => 'tags']); + + $render = $this->view->renderFile(__DIR__ . '/Support/main.php', ['widget' => $selectize]); + + Assert::equalsWithoutLE( + << + + + HTML, + $selectize, + ); + + $this->assertStringContainsString( + <<jQuery(function ($) { + jQuery('#w1').selectize([]); + }); + JS, + $render, + ); + } +} diff --git a/tests/Support/SelectizeModel.php b/tests/Support/SelectizeModel.php new file mode 100644 index 0000000..703c023 --- /dev/null +++ b/tests/Support/SelectizeModel.php @@ -0,0 +1,10 @@ + +beginPage();?> + head();?> + beginBody();?> + + endBody();?> +endPage(); diff --git a/tests/Support/runtime/.gitkeep b/tests/Support/runtime/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..8092bfa --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,62 @@ +destroyApplication(); + } + + protected function mockApplication(): void + { + new Application( + [ + 'id' => 'testapp', + 'aliases' => [ + '@bower' => dirname(__DIR__) . '/node_modules', + '@npm' => dirname(__DIR__) . '/node_modules', + ], + 'basePath' => __DIR__, + 'vendorPath' => dirname(__DIR__) . '/vendor', + 'components' => [ + 'assetManager' => [ + 'basePath' => __DIR__ . '/Support/runtime', + 'baseUrl' => '/', + ], + 'request' => [ + 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq', + 'scriptFile' => __DIR__ . '/index.php', + 'scriptUrl' => '/index.php', + ], + ], + ], + ); + } + + /** + * Destroys application in Yii::$app by setting it to null. + */ + protected function destroyApplication() + { + Yii::$app = null; + Yii::$container = new Container(); + Assert::removeFilesFromDirectory(__DIR__ . '/Support/runtime'); + } +}