diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..3d8d32c --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,7 @@ +Contributing to Yii2 +==================== + +- [Report an issue](https://github.com/yiisoft/yii2/blob/master/docs/internals/report-an-issue.md) +- [Translate documentation or messages](https://github.com/yiisoft/yii2/blob/master/docs/internals/translation-workflow.md) +- [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-general-discussions-for-yii-20/) +- [Contribute to the core code or fix bugs](https://github.com/yiisoft/yii2/blob/master/docs/internals/git-workflow.md) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5f5c645 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +open_collective: yiisoft +github: [yiisoft] +tidelift: "packagist/yiisoft/yii2-bootstrap4" diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..fb58a29 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ + + +### What steps will reproduce the problem? + +### What's expected? + +### What do you get instead? + + +### Additional info + +| Q | A +| ---------------- | --- +| Yii vesion | +| PHP version | +| Operating system | diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..968a845 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +| Q | A +| ------------- | --- +| Is bugfix? | yes/no +| New feature? | yes/no +| Breaks BC? | yes/no +| Tests pass? | yes/no +| Fixed issues | comma-separated list of tickets # fixed by the PR, if any diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..f713847 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +Please use the [security issue form](https://www.yiiframework.com/security) to report to us any security issue you find in Yii. +DO NOT use the issue tracker or discuss it in the public forum as it will cause more damage than help. + +Please note that as a non-commerial OpenSource project we are not able to pay bounties at the moment. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5f8fea2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: build + +on: [push, pull_request] + +env: + DEFAULT_COMPOSER_FLAGS: "--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi" + +jobs: + phpunit: + name: PHP ${{ matrix.php }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + php: ['7.4', '8.0'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies + run: composer update $DEFAULT_COMPOSER_FLAGS + - name: Run unit tests with coverage + run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover --colors=always + if: matrix.php == '7.4' + - name: Run unit tests without coverage + run: vendor/bin/phpunit --verbose --colors=always + if: matrix.php != '7.4' + - name: Upload code coverage + run: | + wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover + if: matrix.php == '7.4' + continue-on-error: true # if is fork diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0929951 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +Yii Framework 2 bootstrap5 extension Change Log +============================================== + +1.0.0 under development +----------------------- +- Initial release diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f6657fb --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. +* Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e582067 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +

+ + + +

Twitter Bootstrap 5 Extension for Yii 2

+
+

+ +This is the Twitter Bootstrap extension for [Yii framework 2.0](http://www.yiiframework.com). It encapsulates [Bootstrap 5](http://getbootstrap.com/) components +and plugins in terms of Yii widgets, and thus makes using Bootstrap components/plugins +in Yii applications extremely easy. + +For license information check the [LICENSE](LICENSE.md)-file. + +Documentation is at [docs/guide/README.md](docs/guide/README.md). + +[![Latest Stable Version](https://poser.pugx.org/simialbi/yii2-bootstrap5/v/stable.png)](https://packagist.org/packages/simialbi/yii2-bootstrap5) +[![Total Downloads](https://poser.pugx.org/simialbi/yii2-bootstrap5/downloads.png)](https://packagist.org/packages/simialbi/yii2-bootstrap5) +[![Build Status](https://github.com/simialbi/yii2-bootstrap5/workflows/build/badge.svg)](https://github.com/simialbi/yii2-bootstrap5/actions) + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist simialbi/yii2-bootstrap5 +``` + +or add + +``` +"simialbi/yii2-bootstrap5": "~1.0@dev" +``` + +to the require section of your `composer.json` file. + +Usage +---- + +For example, the following +single line of code in a view file would render a Bootstrap Progress plugin: + +```php + 60, 'label' => 'test']) ?> +``` diff --git a/composer.json b/composer.json index 7306979..b71ce7e 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "mylistryx/yii2-bootstrap5", + "name": "simialbi/yii2-bootstrap5", "description": "The Twitter Bootstrap v5 extension for the Yii framework", "version": "1.0.0", "keywords": [ @@ -10,25 +10,29 @@ "type": "yii2-extension", "license": "BSD-3-Clause", "support": { - "source": "https://github.com/mylistryx/yii2-bootstrap45" + "source": "https://github.com/simialbi/yii2-bootstrap5" }, "authors": [ { "name": "Sergey Zhukovskiy", "email": "mylistryx@gmail.com", "homepage": "https://net23.ru/" + }, + { + "name": "Simon Karlen", + "email": "simi.albi@outlook.com" } ], - "minimum-stability": "dev", + "minimum-stability": "stable", "require": { - "php": "~7.4.0", - "yiisoft/yii2": "~2.0", - "npm-asset/bootstrap": "^5.0.0", - "ext-json": "*" + "php": ">=7.4.0", + "ext-json": "*", + "yiisoft/yii2": "^2.0.42", + "npm-asset/bootstrap": "^5.0.0" }, "require-dev": { "yiisoft/yii2-coding-standards": "~2.0", - "cweagans/composer-patches": "^1.7" + "phpunit/phpunit": "^7.5.20" }, "repositories": [ { @@ -41,6 +45,11 @@ "yii\\bootstrap5\\": "src" } }, + "autoload-dev": { + "psr-4": { + "yiiunit\\extensions\\bootstrap5\\": "tests" + } + }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" diff --git a/docs/guide-de/README.md b/docs/guide-de/README.md new file mode 100644 index 0000000..0a203d8 --- /dev/null +++ b/docs/guide-de/README.md @@ -0,0 +1,30 @@ +Twitter Bootstrap Erweiterung für Yii 2 +======================================= + +Diese Erweiterung enthält Unterstützung für das [Bootstrap 4 Framework](http://getbootstrap.com/) (auch bekannt als "Twitter Bootstrap") +Markup und dessen Komponenten. Bootstrap ist eine exzellentes, reponsives Framework welches den clientseitigen +Entwicklungsprozess vehement zu verkürzen vermag. + +Die Basis von Bootstrap ist unterteilt in folgende 2 Bereiche: + - CSS Basics: Grid Layout System, Typography, Helper-Klassen, Responsive Funktionen. + - "Ready to use"-Komponenten: Formulare, Menus, Paginationen, Modals, Tabs etc. + +Loslegen +-------- + +* [Installation](installation.md) +* [Assets Setup](assets-setup.md) +* [Basic Usage](basic-usage.md) + +Verwendung +---------- + +* [Yii widgets](usage-widgets.md) +* [Html helper](helper-html.md) +* [Asset Bundles](asset-bundles.md) + +Weitere Themen +-------------- + +* [Using the .sass files of Bootstrap directly](topics-sass.md) +* [Migration von yii2-bootstrap](migrating-yii2-bootstrap.md) diff --git a/docs/guide-de/asset-bundles.md b/docs/guide-de/asset-bundles.md new file mode 100644 index 0000000..0b3a7db --- /dev/null +++ b/docs/guide-de/asset-bundles.md @@ -0,0 +1,15 @@ +Asset Bundles +============= + +Bootstrap ist eine komplexe Front-End-Lösung, welche CSS, Javascript, Schriften usw. beinhaltet. +Um Ihnen die flexibelste Kontrolle über die einzelnen Komponenten zu ermöglichen enthält diese Erweiterung verschiedene Asset Bundles. + +Das sind: +- [[yii\bootstrap4\BootstrapAsset|BootstrapAsset]] - enthält nur das hauptsächliche CSS. +- [[yii\bootstrap4\BootstrapPluginAsset|BootstrapPluginAsset]] - enthält das Javascript. Abhängig von [[yii\bootstrap4\BootstrapAsset]]. + +Verschiedene Anwendunganforderungen erfordern verschiedene Bundles (bzw. Kombinationen). +Falls Sie nur auf das CSS angewiesen sind, reicht es wenn Sie [[yii\bootstrap4\BootstrapAsset]] laden. +Wenn Sie das Javascript verwenden möchten, müssen Sie [[yii\bootstrap4\BootstrapPluginAsset]] auch laden. + +> Tipp: Die meisten Widgets laden [[yii\bootstrap4\BootstrapPluginAsset]] automatisch. diff --git a/docs/guide-de/assets-setup.md b/docs/guide-de/assets-setup.md new file mode 100644 index 0000000..4c01ac8 --- /dev/null +++ b/docs/guide-de/assets-setup.md @@ -0,0 +1,170 @@ +Asset-Konfiguration +=================== + +Diese Erweiterung beruht auf [Bower](http://bower.io/) und/oder [NPM](https://www.npmjs.org/) Packages für die Asset Installation. +Bevor Sie diese Erweiterung einsetzen, sollten Sie entscheiden, auf welche Weise Sie diese Packages installieren möchten. + +## Verwendung des asset-packagist Repository + +Sie können [asset-packagist.or](https://asset-packagist.org) als Package-Quelle für die Bootstrap-Assets angeben. +Fügen Sie dazu folgende Zeilen zur `composer.json`-Datei ihres Projekts hinzu: + +```json +"repositories": [ + { + "type": "composer", + "url": "https://asset-packagist.org" + } +] +``` + +Passen Sie `@npm` und `@bower` in der Konfiguration ihrer Applikation wie folgt an: + +```php +return [ + //... + 'aliases' => [ + '@bower' => '@vendor/bower-asset', + '@npm' => '@vendor/npm-asset', + ], + //... +]; +``` + + +## Verwendung des composer asset Plugins + +Installieren Sie das [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/) global durch das Ausführen folgendes Befehls: + +``` +composer global require "fxp/composer-asset-plugin:^1.4.0" +``` + +Fügen Sie folgende Zeilen zur `composer.json`-Datei ihres Projekts hinzu um das Verzeichnis der Installation von Assets anzupassen +falls Sie möchten, das Yii sie verwaltet: + +```json +"extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } +} +``` + +Daraufhin können Sie den composer install bzw. update Befehl ausführen um die Boostrap Assets zu installieren. + +> Warnung: Das Plugin `fxp/composer-asset-plugin` verlangsamt den `composer update` Befehl signifikant verglichen zur + "asset-packagist"-Methode. + +## Direkte Verwendung des Bower/NPM Clients + +Sie kännen die Bootstrap Assets direkt via Bower oder NPM Client installieren. +Fügen Sie dafür folgende Zeilen zur `package.json`-Datei Ihres Projekts hinzu: + +```json +{ + ... + "dependencies": { + "bootstrap": "4.2.1", + ... + } + ... +} +``` + +Fügen Sie zur `composer.json`-Datei Ihres Projekts folgende Zeilen hinzu zum Verhindern von redundanten Bootstrap-Installationen: + +```json +"replace": { + "npm-asset/bootstrap": ">=4.2.1" +}, +``` + +## Verwendung des CDN + +Sie können die Bootstrap Assets vom [offiziellen CDN](https://www.bootstrapcdn.com) laden. + +Fügen Sie zur `composer.json`-Datei Ihres Projekts folgende Zeilen hinzu zum Verhindern von redundanten Bootstrap-Installationen: + +```json +"replace": { + "npm-asset/bootstrap": ">=4.2.1" +}, +``` + +Konfigurieren Sie die 'assetManager'-Komponente wie folgt (überschreibt die Bootstrap Asset mit den CDN Links): + +```php +return [ + 'components' => [ + 'assetManager' => [ + // override bundles to use CDN : + 'bundles' => [ + 'yii\bootstrap4\BootstrapAsset' => [ + 'sourcePath' => null, + 'baseUrl' => 'https://stackpath.bootstrapcdn.com/bootstrap/4.2.1', + 'css' => [ + 'css/bootstrap.min.css' + ], + ], + 'yii\bootstrap4\BootstrapPluginAsset' => [ + 'sourcePath' => null, + 'baseUrl' => 'https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1', + 'js' => [ + 'js/bootstrap.bundle.min.js' + ], + ], + ], + ], + // ... + ], + // ... +]; +``` + + +## Kompilieren von den .sass Dateien + +Falls Sie den Bootstrap Quelltext direkt anpassen möchten, können Sie das CSS direkt von den Quell *.sass-Dateien kompilieren. +In diesem Fall macht die Installation der Bootstrap Assets via composer bzw. Bower/NPM kein Sinn, da Sie keine Dateien innerhalb +des 'vendor'-Verzeichnisses bearbeiten können. +Sie müssen die Bootstrap-Assets manuell herunterladen und sie irgendwo in Ihrem Projekt platzieren (z.B. 'assets/source/bootstrap'). + +Fügen Sie zur `composer.json`-Datei Ihres Projekts folgende Zeilen hinzu zum Verhindern von redundanten Bootstrap-Installationen: + +```json +"replace": { + "npm-asset/bootstrap": ">=4.2.1" +}, +``` + +Konfigurieren Sie die 'assetManager'-Komponente wie folgt (überschreibt die Bootstrap Assets): + +```php +return [ + 'components' => [ + 'assetManager' => [ + // override bundles to use local project files : + 'bundles' => [ + 'yii\bootstrap4\BootstrapAsset' => [ + 'sourcePath' => '@app/assets/source/bootstrap/dist', + 'css' => [ + YII_ENV_DEV ? 'css/bootstrap.css' : 'css/bootstrap.min.css', + ], + ], + 'yii\bootstrap4\BootstrapPluginAsset' => [ + 'sourcePath' => '@app/assets/source/bootstrap/dist', + 'js' => [ + YII_ENV_DEV ? 'js/bootstrap.js' : 'js/bootstrap.min.js', + ] + ], + ], + ], + // ... + ], + // ... +]; +``` + +Nach dem Verändern des Bootstrap Quellcodes, stellen Sie sicher, dass sie neu [kompiliert werden](https://getbootstrap.com/docs/4.1/getting-started/build-tools/), z.B. mittels `npm run dist`. diff --git a/docs/guide-de/basic-usage.md b/docs/guide-de/basic-usage.md new file mode 100644 index 0000000..84bcd4c --- /dev/null +++ b/docs/guide-de/basic-usage.md @@ -0,0 +1,17 @@ +Grundlegende Verwendung +======================= + +Yii verpackt die Bootstrap Basics nicht in PHP Code, da dass HTML selbst sehr einfach aufgebaut ist. Sie finden mehr +Informationen zur Verwendung der Basics unter [bootstrap documentation website](http://getbootstrap.com/css/). Yii bietet +aber eine einfache Methode zur Einbindung der Bootstrap Assets in Ihre Seite durch das Hinzufügen einer Zeile zu `AppAsset.ph` +im `@app/assets` Verzeichnis: + +```php +public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap4\BootstrapAsset', // Diese Zeile +]; +``` + +Die Verwendung von Bootstrap mittels des Yii Asset Manager erlaubt die Komprimierung und Kombinierung der Bootstrapressourcen +mit den Applikationsressourcen (falls nötig). diff --git a/docs/guide-de/helper-html.md b/docs/guide-de/helper-html.md new file mode 100644 index 0000000..929dfbb --- /dev/null +++ b/docs/guide-de/helper-html.md @@ -0,0 +1,28 @@ +HTML helper +=========== + +Bootstrap führt viele konsistente HTML Strukturen ein, welche es erlauben, verschiedene visuelle Effekte einfach zu verwenden. +Ausschliesslich die komplexesten von ihnen sind mittels Widgets in dieser Erweiterung umgesetzt worden. Der Rest kann manuell +mittels HTML zusammengestellt werden. +Einige spezielle Bootstrap Markups sind implementiert im [[\yii\bootstrap4\Html]]-Helper. +Die [[\yii\bootstrap4\Html]]-Klasse ist eine Erweiterung der regulären [[\yii\helpers\Html]]-Klasse mit Anpassungen zur +Verwendung mit Bootstrap. Sie bietet verschiedene nützliche Methoden. + +Die [[\yii\bootstrap4\Html]]-Klasse erbt von der [[\yii\helpers\Html]]-Klasse und ersetzt diese dadurch vollumfänglich. +Sie benötigen folglich **nicht** beide in Ihren Views. +Beispiel: + +```php + + Html::tag('i', ['class' => 'fas fa-check']) . Html::encode('Save & apply'), + 'encodeLabel' => false, + 'options' => ['class' => 'btn-primary'], +]); ?> +``` + +> Vorsicht: Verwechseln Sie [[\yii\bootstrap4\Html]] und [[\yii\helpers\Html]] Klassen nicht und bedenken Sie jeweils + welche Sie in Ihren Views verwenden. diff --git a/docs/guide-de/installation.md b/docs/guide-de/installation.md new file mode 100644 index 0000000..0981aeb --- /dev/null +++ b/docs/guide-de/installation.md @@ -0,0 +1,20 @@ +Installation +============ + +## Mittels Composer Package + +Der empfohlene Weg zur Installation dieser Erweiterung ist mittels [composer](http://getcomposer.org/download/). + +Führen Sie entweder folgenden Befehlt aus + +``` +php composer.phar require --prefer-dist yiisoft/yii2-bootstrap4 +``` + +oder fügen Sie folgendes + +``` +"yiisoft/yii2-bootstrap4": "~1.0.0" +``` + +zur `require`-Sektion Ihrer `composer.json`-Datei hinzu. diff --git a/docs/guide-de/migrating-yii2-bootstrap.md b/docs/guide-de/migrating-yii2-bootstrap.md new file mode 100644 index 0000000..dc92e11 --- /dev/null +++ b/docs/guide-de/migrating-yii2-bootstrap.md @@ -0,0 +1,110 @@ +Migration von yii2-bootstrap +============================ + +yii2-bootstrap4 ist eine komplette Überarbeitung des Projekts (siehe den Bootstrap 4 von Bootstrap 3 Migrationsguide). +Die grössten Änderungen finden Sie hier zusammengefasst: + +## Allgemein + +* Der Namespace ist nun `yii\bootstrap4` anstatt `yii\bootstrap` +* Es wird das `npm` Paket verwendet anstatt das `bower` Paket +* Es gibt kein Theme Asset mehr +* `popper.js` muss nicht mehr extra installiert werden (wird vom Bootstrap JS Bundle mitgeliefert) + +## Widgets / Klassen + +* [[yii\bootstrap\Collapse|Collapse]] wurde umbenannt zu [[yii\bootstrap4\Accordion|Accordion]] +* [[yii\bootstrap\BootstrapThemeAsset|BootstrapThemeAsset]] wurde entfernt +* [[yii\bootstrap4\Breadcrumbs|Breadcrumbs]] wurde hinzugefügt (Bootstrap 4 Implementation von [[yii\widgets\Breadcrumbs]]) +* [[yii\bootstrap4\ButtonToolbar|ButtonToolbar]] wurde hinzugefügt (https://getbootstrap.com/docs/4.3/components/button-group/#button-toolbar) + + +### BaseHtml + +Die Methode `icon` wurde entfernt. Sie macht keinen Sinn mehr, da Bootstrap 4 keine Icons mehr mit sich bringt. Eine +mögliche Alternative wäre das [Font Awesome Widget](https://github.com/rmrevin/yii2-fontawesome) oder aber das +[Font Awesome Inline Widget](https://github.com/YiiRocks/yii2-fontawesome-inline). + +### ActiveField + +Folgende Properties wurden umbenannt: +* `$checkboxTemplate` zu `$checkTemplate`, +* `$horizontalCheckboxTemplate` zu `$checkHorizontalTemplate`, +* `$horizontalRadioTemplate` zu `$radioHorizontalTemplate` + +Die Properties `$inlineCheckboxListTemplate` und `$inlineRadioListTemplate` wurden entfernt. Dafür gibt es ein neues +Template mit dem Namen `$checkEnclosedTemplate`. In Bootstrap 4 sind Checkboxen standardmässig nicht mehr von Labeln +eingeschlossen. + +### ActiveForm + +Hier wurden die Konstanten [[yii\bootstrap4\ActiveForm::LAYOUT_DEFAULT]], [[yii\bootstrap4\ActiveForm::LAYOUT_HORIZONTAL]] +und [[yii\bootstrap4\ActiveForm::LAYOUT_INLINE]] eingeführt. Sie sollen die Verwendung der Layout vereinfachen und bei +allfälligen Änderungen der Werte Folgefehler verhindern. + +### Breadcrumbs + +Dieses Widget wurde neu eingeführt um die Korrekte Darstellung von Breadcrumbs im Bootstrap 4 Design zu gerwährleisten. +Es ist vollständig komptibel mit [[yii\widgets\Breadcrumbs]]. + +### ButtonDropdown + +Dieses Widget hat ein neues Property mit Namen `$direction` erhalten. Es ermöglicht die Anzeige des Menüs auf z.B. der +rechten Seite des Buttons. Des weiteren gibt es die Konstanten [[yii\bootstrap4\ButtonDropdown::DIRECTION_DOWN]], +[[yii\bootstrap4\ButtonDropdown::DIRECTION_LEFT]], [[yii\bootstrap4\ButtonDropdown::DIRECTION_RIGHT]] und +[[yii\bootstrap4\ButtonDropdown::DIRECTION_UP]] um die Richtungsselektion zu vereinfachen. +Es wurde ein weiteres Property eingeführt mit dem Namen `$renderContainer`. Falls dieses auf `false` gestellt wird, wird +das rendern des das Dropdown umfassenden DIVs verhindert. + +Folgende Properties wurden umbenannt: +* `$containerOptions` zu `$options`, +* `$options` zu `$buttonOptions` + +### ButtonToolbar + +Dieses Widget wurde eingeführt um einfach Button-Toolbars zu erstellen. Weitere Informationen erhalten Sie unter +https://getbootstrap.com/docs/4.3/components/button-group/#button-toolbar. + +### Carousel + +Dieses Widget hat das Property `$crossfade` erhalten. Es erlaubt das Ändern der Animation zwischen den Slides auf ein Fade, +anstatt eines Slided wenn es auf `true` gestellt wird. + +### LinkPager + +Dieses neue Widget repräsentiert die Bootstrap Version von [[yii\widgets\LinkPager]]. Es rendert eine Pagination im Bootstrap +Stil. Weitere Informationen erhalten Sie unter https://getbootstrap.com/docs/4.3/components/pagination/. + +### Modal + +Folgende Properties wurden umbenannt: +* `$header` zu `$title`, +* `$headerOptions` zu `$titleOptions` + +Des Weiteren ist es nicht mehr von nöten, beim `$title` die Titel-Tags `

Collapsible Group Item #3

', + 'content' => [ + '

test content1

', + '

test content2

' + ], + 'contentOptions' => [ + 'class' => 'testContentOptions2' + ], + 'options' => [ + 'class' => 'testClass2', + 'id' => 'testId2' + ], + 'encode' => false, + 'footer' => 'Footer2' + ], + [ + 'label' => '

Collapsible Group Item #4

', + 'content' => [ + '

test content1

', + '

test content2

' + ], + 'contentOptions' => [ + 'class' => 'testContentOptions3' + ], + 'options' => [ + 'class' => 'testClass3', + 'id' => 'testId3' + ], + 'encode' => true, + 'footer' => 'Footer3' + ], + ] + ]); + + $this->assertEqualsWithoutLE(<< +
+
+
+ + +
+
+
+
+
Das ist das Haus vom Nikolaus
+ + +
+
+
+
+ + + +
+
+
+
+ + + +
+ + +HTML + , $output); + } + + public function testLabelKeys() + { + ob_start(); + $form = ActiveForm::begin(['action' => '/something']); + ActiveForm::end(); + ob_end_clean(); + + Accordion::$counter = 0; + $output = Accordion::widget([ + 'items' => [ + 'Item1' => 'Content1', + 'Item2' => [ + 'content' => 'Content2', + ], + [ + 'label' => 'Item3', + 'content' => 'Content3', + ], + 'FormField' => $form->field(new DynamicModel(['test']), 'test',['template' => '{input}']), + ] + ]); + + $this->assertEqualsWithoutLE(<< +
+
+
+
Content1
+ +
+
+
+
+
Content2
+ +
+
+
+
+
Content3
+ +
+
+
+
+
+ +
+ +
+ + +HTML + , $output); + } + + public function testExpandOptions() + { + Accordion::$counter = 0; + $output = Accordion::widget([ + 'items' => [ + 'Item1' => 'Content1', + 'Item2' => [ + 'content' => 'Content2', + 'expand' => true, + ], + ] + ]); + + $this->assertEqualsWithoutLE(<< +
+
+
+
Content1
+ +
+
+
+
+
Content2
+ +
+ + +HTML + , $output); + } + + public function invalidItemsProvider() + { + return [ + [ ['content'] ], // only content without label key + [ [[]] ], // only content array without label + [ [['content' => 'test']] ], // only content array without label + ]; + } + + /** + * @dataProvider invalidItemsProvider + * @expectedException \yii\base\InvalidConfigException + */ + public function testMissingLabel($items) + { + Accordion::widget([ + 'items' => $items, + ]); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/8357 + */ + public function testRenderObject() + { + $template = ['template' => '{input}']; + ob_start(); + $form = ActiveForm::begin(['action' => '/something']); + ActiveForm::end(); + ob_end_clean(); + $model = new data\Singer; + + Accordion::$counter = 0; + $output = Accordion::widget([ + 'items' => [ + [ + 'label' => 'Collapsible Group Item #1', + 'content' => $form->field($model, 'firstName', $template) + ], + ] + ]); + + $this->assertEqualsWithoutLE(<< +
+
+
+
+ +
+ +
+ + +HTML + , $output); + } + + public function testAutoCloseItems() + { + $items = [ + [ + 'label' => 'Item 1', + 'content' => 'Content 1', + ], + [ + 'label' => 'Item 2', + 'content' => 'Content 2', + ], + ]; + + $output = Accordion::widget([ + 'items' => $items + ]); + $this->assertContains('data-parent="', $output); + $output = Accordion::widget([ + 'autoCloseItems' => false, + 'items' => $items + ]); + $this->assertNotContains('data-parent="', $output); + } + + /** + * @depends testRender + */ + public function testItemToggleTag() + { + $items = [ + [ + 'label' => 'Item 1', + 'content' => 'Content 1', + ], + [ + 'label' => 'Item 2', + 'content' => 'Content 2', + ], + ]; + + Accordion::$counter = 0; + + $output = Accordion::widget([ + 'items' => $items, + 'itemToggleOptions' => [ + 'tag' => 'a', + 'class' => 'custom-toggle', + ], + ]); + $this->assertContains('
assertNotContains(' $items, + 'itemToggleOptions' => [ + 'tag' => 'a', + 'class' => ['widget' => 'custom-toggle'], + ], + ]); + $this->assertContains('
assertNotContains('collapse-toggle', $output); + } +} diff --git a/tests/ActiveFieldDefaultFormCheckTest.php b/tests/ActiveFieldDefaultFormCheckTest.php new file mode 100644 index 0000000..3e8f841 --- /dev/null +++ b/tests/ActiveFieldDefaultFormCheckTest.php @@ -0,0 +1,241 @@ +mockWebApplication([ + 'container' => [ + 'definitions' => [ + 'yii\bootstrap5\ActiveField' => [ + 'checkTemplate' => "
\n{input}\n{label}\n{error}\n{hint}\n
", + 'radioTemplate' => "
\n{input}\n{label}\n{error}\n{hint}\n
", + 'checkHorizontalTemplate' => "{beginWrapper}\n
\n{input}\n{label}\n{error}\n{hint}\n
\n{endWrapper}", + 'radioHorizontalTemplate' => "{beginWrapper}\n
\n{input}\n{label}\n{error}\n{hint}\n
\n{endWrapper}", + 'checkOptions' => [ + 'class' => ['widget' => 'form-check-input'], + 'labelOptions' => [ + 'class' => ['widget' => 'form-check-label'] + ], + 'wrapperOptions' => [ + 'class' => ['widget' => 'form-check'] + ] + ], + 'radioOptions' => [ + 'class' => ['widget' => 'form-check-input'], + 'labelOptions' => [ + 'class' => ['widget' => 'form-check-label'] + ], + 'wrapperOptions' => [ + 'class' => ['widget' => 'form-check'] + ] + ] + ] + ] + ] + ]); + + $this->_helperModel = new DynamicModel(['attributeName']); + ob_start(); + $this->_helperForm = ActiveForm::begin(['action' => '/something']); + ActiveForm::end(); + ob_end_clean(); + + $this->_activeField = Yii::createObject([ + 'class' => 'yii\bootstrap5\ActiveField', + 'form' => $this->_helperForm + ]); + $this->_activeField->model = $this->_helperModel; + $this->_activeField->attribute = $this->_attributeName; + } + + public function testDefaultCheckboxByConfig() + { + Html::$counter = 0; + $this->_activeField->inline = true; + $html = $this->_activeField->checkbox()->render(); + + $expectedHtml = << +
+ + +
+ +
+ +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testDefaultRadioByConfig() + { + Html::$counter = 0; + $this->_activeField->inline = true; + $html = $this->_activeField->radio()->render(); + + $expectedHtml = << +
+ + +
+ +
+ +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testDefaultCheckboxListByConfig() + { + Html::$counter = 0; + $html = $this->_activeField->checkboxList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testDefaultRadioListByConfig() + { + Html::$counter = 0; + $html = $this->_activeField->radioList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testHorizontalLayout() + { + Html::$counter = 0; + ActiveForm::$counter = 0; + ob_start(); + $model = new DynamicModel(['attributeName', 'checkbox', 'gridRadios']); + $form = ActiveForm::begin([ + 'action' => '/some-action', + 'layout' => ActiveForm::LAYOUT_HORIZONTAL + ]); + echo $form->field($model, 'attributeName'); + echo $form->field($model, 'checkbox')->checkbox(['wrapperOptions' => ['class' => ['widget' => new UnsetArrayValue()]]]); + echo $form->field($model, 'gridRadios')->radioList([ + 'option1' => 'First radio', + 'option2' => 'Second radio', + 'option3' => 'Third radio' + ]); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + +
+ +
+ +
+ +HTML; + $expected2 = << +
+
+ + +
+ +
+
+ +HTML; + $expected3 = << + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ +HTML; + + + $this->assertContainsWithoutLE($expected, $out); + $this->assertContainsWithoutLE($expected2, $out); + $this->assertContainsWithoutLE($expected3, $out); + } +} diff --git a/tests/ActiveFieldTest.php b/tests/ActiveFieldTest.php new file mode 100644 index 0000000..cdfe082 --- /dev/null +++ b/tests/ActiveFieldTest.php @@ -0,0 +1,336 @@ +helperModel = new DynamicModel(['attributeName']); + ob_start(); + $this->helperForm = ActiveForm::begin(['action' => '/something']); + ActiveForm::end(); + ob_end_clean(); + + $this->activeField = new ActiveField(['form' => $this->helperForm]); + $this->activeField->model = $this->helperModel; + $this->activeField->attribute = $this->attributeName; + } + + public function testFileInput() + { + Html::$counter = 0; + $html = $this->activeField->fileInput()->render(); + + $expectedHtml = << + + + +
+ +HTML; + + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + // Tests : + + public function testRadioList() + { + Html::$counter = 0; + $html = $this->activeField->radioList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testRadioError() + { + Html::$counter = 0; + $this->helperModel->addError($this->attributeName, 'Test print error message'); + $html = $this->activeField->radio()->render(); + + $expectedHtml = << +
+ + +
Test print error message
+ +
+ +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testRadioListError() + { + Html::$counter = 0; + $this->helperModel->addError($this->attributeName, 'Test print error message'); + $html = $this->activeField->radioList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
Test print error message
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testCheckboxList() + { + Html::$counter = 0; + $html = $this->activeField->checkboxList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testCheckboxError() + { + Html::$counter = 0; + $this->helperModel->addError($this->attributeName, 'Test print error message'); + $html = $this->activeField->checkbox()->render(); + + $expectedHtml = << +
+ + +
Test print error message
+ +
+ +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testCheckboxListError() + { + Html::$counter = 0; + $this->helperModel->addError($this->attributeName, 'Test print error message'); + $html = $this->activeField->checkboxList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
Test print error message
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testRadioListInline() + { + Html::$counter = 0; + $this->activeField->inline = true; + $html = $this->activeField->radioList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + public function testCheckboxListInline() + { + Html::$counter = 0; + $this->activeField->inline = true; + $html = $this->activeField->checkboxList([1 => 'name1', 2 => 'name2'])->render(); + + $expectedHtml = << + +
+ + +
+ +
+ + +
+
+
+ + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + /** + * @depends testRadioList + * + * @see https://github.com/yiisoft/yii2-bootstrap/issues/81 + */ + public function testRadioListItemOptions() + { + Html::$counter = 0; + $content = $this->activeField->radioList([1 => 'name1', 2 => 'name2'], [ + 'itemOptions' => [ + 'data-attribute' => 'test' + ] + ])->render(); + + $this->assertContains('data-attribute="test"', $content); + } + + /** + * @depends testCheckboxList + * + * @see https://github.com/yiisoft/yii2-bootstrap/issues/81 + */ + public function testCheckboxListItemOptions() + { + Html::$counter = 0; + $content = $this->activeField->checkboxList([1 => 'name1', 2 => 'name2'], [ + 'itemOptions' => [ + 'data-attribute' => 'test' + ] + ])->render(); + + $this->assertContains('data-attribute="test"', $content); + } + + /** + * + */ + public function testCustomRadio() + { + Html::$counter = 0; + $this->activeField->inline = true; + $html = $this->activeField->radio()->render(); + + $expectedHtml = << +
+ + +
+ +
+ +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + /** + * + */ + public function testCustomCheckbox() + { + Html::$counter = 0; + $this->activeField->inline = true; + $html = $this->activeField->checkbox()->render(); + + $expectedHtml = << +
+ + +
+ +
+ +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } +} diff --git a/tests/ActiveFormTest.php b/tests/ActiveFormTest.php new file mode 100644 index 0000000..8d9b2f3 --- /dev/null +++ b/tests/ActiveFormTest.php @@ -0,0 +1,321 @@ + '/some-action', + 'layout' => ActiveForm::LAYOUT_DEFAULT + ]); + echo $form->field($model, 'attributeName'); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + + + +
+ +HTML; + + + $this->assertContainsWithoutLE($expected, $out); + } + + public function testHorizontalLayout() + { + Html::$counter = 0; + ActiveForm::$counter = 0; + ob_start(); + $model = new DynamicModel(['attributeName', 'checkbox', 'gridRadios']); + $form = ActiveForm::begin([ + 'action' => '/some-action', + 'layout' => ActiveForm::LAYOUT_HORIZONTAL + ]); + echo $form->field($model, 'attributeName'); + echo $form->field($model, 'checkbox')->checkbox(); + echo $form->field($model, 'gridRadios')->radioList([ + 'option1' => 'First radio', + 'option2' => 'Second radio', + 'option3' => 'Third radio' + ]); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + +
+ +
+ +
+ +HTML; + $expected2 = << +
+
+ + +
+ +
+
+ +HTML; + $expected3 = << + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ +HTML; + + + $this->assertContainsWithoutLE($expected, $out); + $this->assertContainsWithoutLE($expected2, $out); + $this->assertContainsWithoutLE($expected3, $out); + } + + /** + * @depends testHorizontalLayout + */ + public function testHorizontalLayoutTemplateOverride() + { + ActiveForm::$counter = 0; + ob_start(); + $model = new DynamicModel(['checkboxName']); + $form = ActiveForm::begin([ + 'action' => '/some-action', + 'layout' => ActiveForm::LAYOUT_HORIZONTAL + ]); + echo $form->field($model, 'checkboxName')->checkbox(['template' => "
\n{input}\n{label}\n
\n
{error}
"]); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + + + +
+HTML; + + + $this->assertContainsWithoutLE($expected, $out); + } + + public function testInlineLayout() + { + ActiveForm::$counter = 0; + ob_start(); + $model = new DynamicModel(['attributeName', 'selectName', 'checkboxName']); + $form = ActiveForm::begin([ + 'action' => '/some-action', + 'layout' => ActiveForm::LAYOUT_INLINE + ]); + echo $form->field($model, 'attributeName'); + echo $form->field($model, 'selectName')->listBox([ + '1' => 'One', + '2' => 'Two', + '3' => 'Three' + ]); + echo $form->field($model, 'checkboxName')->checkbox(); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + + + + + +HTML; + $expected2 = << + + + + + +HTML; + $expected3 = << +
+ + + + +
+ +HTML; + + + $this->assertContainsWithoutLE('
assertContainsWithoutLE($expected, $out); + $this->assertContainsWithoutLE($expected2, $out); + $this->assertContainsWithoutLE($expected3, $out); + } + + public function testHintRendering() + { + ActiveForm::$counter = 0; + ob_start(); + $model = new User(); + $form = ActiveForm::begin([ + 'action' => '/some-action', + 'layout' => ActiveForm::LAYOUT_DEFAULT + ]); + echo $form->field($model, 'firstName'); + echo $form->field($model, 'lastName'); + echo $form->field($model, 'username'); + echo $form->field($model, 'password')->passwordInput(); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + + + +
+ +HTML; + $expected2 = << + + + +
+ +HTML; + $expected3 = << + + +Your username must be at least 4 characters long +
+ +HTML; + $expected4 = << + + +Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji. +
+ +HTML; + + $this->assertContainsWithoutLE($expected, $out); + $this->assertContainsWithoutLE($expected2, $out); + $this->assertContainsWithoutLE($expected3, $out); + $this->assertContainsWithoutLE($expected4, $out); + } + + /** + * Fixes #128 + * @see https://github.com/yiisoft/yii2-bootstrap5/issues/128 + */ + public function testInputTemplate() + { + $model = new User(); + $model->validate(); + + ActiveForm::$counter = 0; + ob_start(); + $form = ActiveForm::begin(); + echo $form->field($model, 'username', ['inputTemplate' => '{input}']); + ActiveForm::end(); + $out = ob_get_clean(); + + $expected = << + + +Your username must be at least 4 characters long +
Username cannot be blank.
+ +HTML; + + $this->assertContainsWithoutLE($expected, $out); + } + + /** + * Fixes #196 + */ + public function testFormNoRoleAttribute() + { + $form = ActiveForm::widget(); + + $this->assertNotContains('role="form"', $form); + } + + public function testErrorSummaryRendering() + { + ActiveForm::$counter = 0; + ob_start(); + $model = new User(); + $model->validate(); + $form = ActiveForm::begin([ + 'action' => '/some-action', + 'layout' => ActiveForm::LAYOUT_DEFAULT + ]); + echo $form->errorSummary($model); + ActiveForm::end(); + $out = ob_get_clean(); + + + $this->assertContainsWithoutLE('
'Holy guacamole! You should check in on some of those fields below.', + 'options' => [ + 'class' => ['alert-warning'] + ] + ]); + + $expectedHtml = << + +Holy guacamole! You should check in on some of those fields below. + + +
+HTML; + + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + /** + * @depends testNormalAlert + */ + public function testDismissibleAlert() + { + Alert::$counter = 0; + $html = Alert::widget([ + 'body' => "Message1", + ]); + + $expectedHtml = << + +Message1 + + + +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } +} diff --git a/tests/BreadcrumbsTest.php b/tests/BreadcrumbsTest.php new file mode 100644 index 0000000..8c32444 --- /dev/null +++ b/tests/BreadcrumbsTest.php @@ -0,0 +1,33 @@ + ['label' => 'Home', 'url' => '#'], + 'links' => [ + ['label' => 'Library', 'url' => '#'], + ['label' => 'Data'] + ] + ]); + + $expected = <<
+HTML; + + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/ButtonDropdownTest.php b/tests/ButtonDropdownTest.php new file mode 100644 index 0000000..f0ffd7c --- /dev/null +++ b/tests/ButtonDropdownTest.php @@ -0,0 +1,82 @@ + ButtonDropdown::DIRECTION_UP, + 'options' => [ + 'class' => $containerClass, + ], + 'label' => 'Action', + 'dropdown' => [ + 'items' => [ + ['label' => 'DropdownA', 'url' => '/'], + ['label' => 'DropdownB', 'url' => '#'], + ], + ], + ]); + + $this->assertContains("$containerClass dropup btn-group", $out); + } + + public function testDirection() + { + ButtonDropdown::$counter = 0; + $out = ButtonDropdown::widget([ + 'direction' => ButtonDropdown::DIRECTION_LEFT, + 'label' => 'Action', + 'dropdown' => [ + 'items' => [ + ['label' => 'ItemA', 'url' => '#'], + ['label' => 'ItemB', 'url' => '#'], + ], + ], + ]); + + $expected = << + + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testSplit() + { + ButtonDropdown::$counter = 0; + $out = ButtonDropdown::widget([ + 'direction' => ButtonDropdown::DIRECTION_DOWN, + 'label' => 'Split dropdown', + 'split' => true, + 'dropdown' => [ + 'items' => [ + ['label' => 'ItemA', 'url' => '#'], + ['label' => 'ItemB', 'url' => '#'] + ] + ] + ]); + + $expected = << + + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/ButtonGroupTest.php b/tests/ButtonGroupTest.php new file mode 100644 index 0000000..40e72b9 --- /dev/null +++ b/tests/ButtonGroupTest.php @@ -0,0 +1,33 @@ + [ + ['label' => 'button-A'], + ['label' => 'button-B', 'visible' => true], + ['label' => 'button-C', 'visible' => false], + Button::widget(['label' => 'button-D']), + ], + ]); + + $expected = << + + +HTML; + + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/ButtonToolbarTest.php b/tests/ButtonToolbarTest.php new file mode 100644 index 0000000..a1f0294 --- /dev/null +++ b/tests/ButtonToolbarTest.php @@ -0,0 +1,106 @@ + [ + 'aria-label' => 'Toolbar with button groups' + ], + 'buttonGroups' => [ + ButtonGroup::widget([ + 'options' => [ + 'aria-label' => 'First group', + 'class' => ['mr-2'] + ], + 'buttons' => [ + ['label' => '1'], + ['label' => '2'], + ['label' => '3'], + ['label' => '4'] + ] + ]), + [ + 'options' => [ + 'aria-label' => 'Second group' + ], + 'buttons' => [ + ['label' => '5'], + ['label' => '6'], + ['label' => '7'] + ] + ] + ] + ]); + + $expected = <<
+ + +
+
+ +
+HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testAdditionalContent() + { + ButtonToolbar::$counter = 0; + $addHtml = << +
+
@
+
+ + +HTML; + $out = ButtonToolbar::widget([ + 'options' => [ + 'aria-label' => 'Toolbar with button groups' + ], + 'buttonGroups' => [ + [ + 'options' => [ + 'aria-label' => 'First group', + 'class' => ['mr-2'] + ], + 'buttons' => [ + ['label' => '1'], + ['label' => '2'], + ['label' => '3'], + ['label' => '4'] + ] + ], + $addHtml + ] + ]); + + $expected = <<
+ + +
+
+
+
@
+
+ +
+HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/CarouselTest.php b/tests/CarouselTest.php new file mode 100644 index 0000000..037661b --- /dev/null +++ b/tests/CarouselTest.php @@ -0,0 +1,95 @@ + [ + [ + 'content' => '', + 'caption' => '
First slide label

Nulla vitae elit libero, a pharetra augue mollis interdum.

', + 'captionOptions' => [ + 'class' => ['d-none', 'd-md-block'] + ] + ], + [ + 'content' => '', + 'caption' => '
Second slide label

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

', + 'captionOptions' => [ + 'class' => ['d-none', 'd-md-block'] + ] + ], + [ + 'content' => '', + 'caption' => '
Third slide label

Praesent commodo cursus magna, vel scelerisque nisl consectetur.

', + 'captionOptions' => [ + 'class' => ['d-none', 'd-md-block'] + ] + ] + ] + ]); + + $expected = << + + +Previous +Next + + +HTML; + $this->assertEqualsWithoutLE($expected, $out); + } + + /** + * @depends testContainerOptions + */ + public function testCrossfade() + { + Carousel::$counter = 0; + $out = Carousel::widget([ + 'crossfade' => true, + 'items' => [ + [ + 'content' => '', + 'caption' => '
First slide label

Nulla vitae elit libero, a pharetra augue mollis interdum.

', + 'captionOptions' => [ + 'class' => ['d-none', 'd-md-block'] + ] + ], + [ + 'content' => '', + 'caption' => '
Second slide label

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

', + 'captionOptions' => [ + 'class' => ['d-none', 'd-md-block'] + ] + ], + [ + 'content' => '', + 'caption' => '
Third slide label

Praesent commodo cursus magna, vel scelerisque nisl consectetur.

', + 'captionOptions' => [ + 'class' => ['d-none', 'd-md-block'] + ] + ] + ] + ]); + + $this->assertContains('class="carousel slide carousel-fade"', $out); + } +} diff --git a/tests/DropdownTest.php b/tests/DropdownTest.php new file mode 100644 index 0000000..0516aed --- /dev/null +++ b/tests/DropdownTest.php @@ -0,0 +1,248 @@ + [ + [ + 'label' => 'Page1' + ], + [ + 'label' => 'Dropdown1', + 'url' => '#test', + 'items' => [ + ['label' => 'Page2'], + ['label' => 'Page3'], + ] + ], + [ + 'label' => 'Dropdown2', + 'visible' => false, + 'items' => [ + ['label' => 'Page4', 'content' => 'Page4'], + ['label' => 'Page5', 'content' => 'Page5'], + ] + ] + ] + ] + ); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testSubMenuOptions() + { + Dropdown::$counter = 0; + $out = Dropdown::widget( + [ + 'submenuOptions' => [ + 'class' => 'submenu-list', + ], + 'items' => [ + [ + 'label' => 'Dropdown1', + 'items' => [ + ['label' => 'Page1', 'content' => 'Page2'], + ['label' => 'Page2', 'content' => 'Page3'], + ] + ], + '-', + [ + 'label' => 'Dropdown2', + 'items' => [ + ['label' => 'Page3', 'content' => 'Page4'], + ['label' => 'Page4', 'content' => 'Page5'], + ], + 'submenuOptions' => [ + 'class' => 'submenu-override', + ], + ] + ] + ] + ); + + $expected = << + + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testActive() + { + Dropdown::$counter = 0; + $out = Dropdown::widget( + [ + 'submenuOptions' => [ + 'class' => 'submenu-list', + ], + 'items' => [ + [ + 'label' => 'Dropdown1', + 'items' => [ + ['label' => 'Page1', 'content' => 'Page2'], + ['label' => 'Page2', 'content' => 'Page3'], + ] + ], + '-', + [ + 'label' => 'Dropdown2', + 'items' => [ + ['label' => 'Page3', 'content' => 'Page3', 'url' => '/', 'active' => true], + ['label' => 'Page4', 'content' => 'Page4'], + ], + ] + ] + ] + ); + $expected = << + + +HTML; + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testDisabled() + { + Dropdown::$counter = 0; + $out = Dropdown::widget( + [ + 'submenuOptions' => [ + 'class' => 'submenu-list', + ], + 'items' => [ + [ + 'label' => 'Dropdown1', + 'items' => [ + ['label' => 'Page1', 'content' => 'Page2'], + ['label' => 'Page2', 'content' => 'Page3'], + ], + 'disabled' => true + ], + '-', + [ + 'label' => 'Dropdown2', + 'items' => [ + ['label' => 'Page3', 'content' => 'Page3'], + ['label' => 'Page4', 'content' => 'Page4'], + ], + ] + ] + ] + ); + + $expected = << + + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testForms() + { + Dropdown::$counter = 0; + $form = << +
+ + +
+
+ + +
+
+ + +
+ + +HTML; + + $out = Dropdown::widget([ + 'items' => [ + $form, + '-', + ['label' => 'New around here? Sign up', 'url' => '#'], + ['label' => 'Forgot password?', 'url' => '#'] + ] + ]); + + $expected = <<
+
+ + +
+
+ + +
+
+ + +
+ +
+ +New around here? Sign up +Forgot password? +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/HtmlTest.php b/tests/HtmlTest.php new file mode 100644 index 0000000..aa29cf2 --- /dev/null +++ b/tests/HtmlTest.php @@ -0,0 +1,145 @@ +' + ], + [ + '', + [], + '' + ] + ]; + } + + /** + * @dataProvider dataProviderStaticControl + * + * @param string $value + * @param array $options + * @param string $expectedHtml + */ + public function testStaticControl($value, array $options, $expectedHtml) + { + $this->assertEquals($expectedHtml, Html::staticControl($value, $options)); + } + + public function testRadioList() + { + $this->assertEquals('
', Html::radioList('test')); + + $dataItems = [ + 'value1' => 'text1', + 'value2' => 'text2', + ]; + + Html::$counter = 0; + + $expected = <<<'EOD' +
+
+
+
+EOD; + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $dataItems)); + + Html::$counter = 0; + $expected = <<<'EOD' +
0 +1
+EOD; + $this->assertEqualsWithoutLE($expected, Html::radioList('test', ['value2'], $dataItems, [ + 'item' => function ($index, $label, $name, $checked, $value) { + return $index . Html::label($label . ' ' . Html::radio($name, $checked, ['value' => $value])); + }, + ])); + + Html::$counter = 0; + $expected = <<<'EOD' +
+
+EOD; + $this->assertEqualsWithoutLE($expected, Html::radioList('test', [], ['value' => 'label&'])); + + Html::$counter = 0; + $expected = <<<'EOD' +
+
+EOD; + $this->assertEqualsWithoutLE($expected, Html::radioList('test', [], ['value' => 'label&'], ['encode' => false])); + } + + public function testCheckboxList() + { + $this->assertEquals('
', Html::checkboxList('test')); + + $dataItems = [ + 'value1' => 'text1', + 'value2' => 'text2', + ]; + + Html::$counter = 0; + + $expected = <<<'EOD' +
+
+
+
+EOD; + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $dataItems)); + + + Html::$counter = 0; + $expected = <<<'EOD' +
0 +1
+EOD; + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', ['value2'], $dataItems, [ + 'item' => function ($index, $label, $name, $checked, $value) { + return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, ['value' => $value])); + }, + ])); + + Html::$counter = 0; + $expected = <<<'EOD' +
+
+EOD; + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', 'value', ['value' => 'label&'])); + + Html::$counter = 0; + $expected = <<<'EOD' +
+
+EOD; + $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', 'value', ['value' => 'label&'], ['encode' => false])); + } + + public function testError() + { + $model = new DynamicModel(); + $model->addError('foo', 'Some error message.'); + + $this->assertEquals('
Some error message.
', Html::error($model, 'foo')); + $this->assertEquals('
Some error message.
', Html::error($model, 'foo', ['class' => 'custom-class'])); + $this->assertEquals('
Some error message.
', Html::error($model, 'foo', ['class' => null])); + $this->assertEquals('

Some error message.

', Html::error($model, 'foo', ['tag' => 'p'])); + } +} diff --git a/tests/LinkPagerTest.php b/tests/LinkPagerTest.php new file mode 100644 index 0000000..f3fb06f --- /dev/null +++ b/tests/LinkPagerTest.php @@ -0,0 +1,154 @@ +mockWebApplication([ + 'components' => [ + 'urlManager' => [ + 'scriptUrl' => '/', + ], + ], + ]); + } + + /** + * Get pagination. + * @param int $page + * @return Pagination + */ + private function getPagination($page) + { + $pagination = new Pagination(); + $pagination->setPage($page); + $pagination->totalCount = 500; + $pagination->route = 'test'; + return $pagination; + } + + public function testFirstLastPageLabels() + { + $pagination = $this->getPagination(5); + $output = LinkPager::widget([ + 'pagination' => $pagination, + 'firstPageLabel' => true, + 'lastPageLabel' => true, + ]); + $this->assertContains('
  • 1
  • ', $output); + $this->assertContains('
  • 25
  • ', $output); + $output = LinkPager::widget([ + 'pagination' => $pagination, + 'firstPageLabel' => 'First', + 'lastPageLabel' => 'Last', + ]); + $this->assertContains('
  • First
  • ', $output); + $this->assertContains('
  • Last
  • ', $output); + $output = LinkPager::widget([ + 'pagination' => $pagination, + 'firstPageLabel' => false, + 'lastPageLabel' => false, + ]); + $this->assertNotContains('
  • ', $output); + $this->assertNotContains('
  • ', $output); + } + + public function testDisabledPageElementOptions() + { + $output = LinkPager::widget([ + 'pagination' => $this->getPagination(0), + 'disabledListItemSubTagOptions' => ['class' => ['foo-bar']], + ]); + $this->assertContains('
  • 6
  • ', $output); + $output = LinkPager::widget([ + 'pagination' => $pagination, + 'disableCurrentPageButton' => true, + ]); + $this->assertContains('
  • 6
  • ', $output); + } + + public function testOptionsWithTagOption() + { + LinkPager::$counter = 0; + $output = LinkPager::widget([ + 'pagination' => $this->getPagination(5), + 'options' => [ + 'tag' => 'div', + ], + ]); + $this->assertTrue(StringHelper::startsWith($output, '
    ')); + $this->assertTrue(StringHelper::endsWith($output, '
    ')); + } + + public function testLinkWrapOptions() + { + $output = LinkPager::widget([ + 'pagination' => $this->getPagination(1), + 'linkContainerOptions' => [ + 'tag' => 'div', + 'class' => 'my-class', + ], + ]); + $this->assertContains( + '', + $output + ); + $this->assertContains( + '', + $output + ); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/15536 + */ + public function testShouldTriggerInitEvent() + { + $initTriggered = false; + LinkPager::widget([ + 'pagination' => $this->getPagination(1), + 'on init' => function () use (&$initTriggered) { + $initTriggered = true; + } + ]); + $this->assertTrue($initTriggered); + } +} diff --git a/tests/ModalTest.php b/tests/ModalTest.php new file mode 100644 index 0000000..8194c38 --- /dev/null +++ b/tests/ModalTest.php @@ -0,0 +1,195 @@ + false, + 'bodyOptions' => ['class' => 'modal-body test', 'style' => 'text-align:center;'] + ]); + + + $expected = << + + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + /** + * @depends testBodyOptions + */ + public function testContainerOptions() + { + Modal::$counter = 0; + + ob_start(); + Modal::begin([ + 'title' => 'Modal title', + 'footer' => Html::button('Close', [ + 'type' => 'button', + 'class' => ['btn', 'btn-secondary'], + 'data' => [ + 'dismiss' => 'modal' + ] + ]) . "\n" . Html::button('Save changes', [ + 'type' => 'button', + 'class' => ['btn', 'btn-primary'] + ]) + ]); + echo '

    Woohoo, you\'re reading this text in a modal!

    '; + Modal::end(); + $out = ob_get_clean(); + + $expected = << + + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testTriggerButton() + { + Modal::$counter = 0; + + ob_start(); + Modal::begin([ + 'toggleButton' => [ + 'class' => ['btn', 'btn-primary'], + 'label' => 'Launch demo modal' + ], + 'title' => 'Modal title', + 'footer' => Html::button('Close', [ + 'type' => 'button', + 'class' => ['btn', 'btn-secondary'] + ]) . "\n" . Html::button('Save changes', [ + 'type' => 'button', + 'class' => ['btn', 'btn-primary'] + ]) + ]); + echo '

    Woohoo, you\'re reading this text in a modal!

    '; + Modal::end(); + $out = ob_get_clean(); + + $this->assertContains('', + $out); + } + + public function testDialogOptions() + { + Modal::$counter = 0; + $out = Modal::widget([ + 'closeButton' => false, + 'dialogOptions' => ['class' => 'test', 'style' => 'text-align:center;'] + ]); + + + $expected = << + + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testCenterVertical() + { + Modal::$counter = 0; + $out = Modal::widget([ + 'closeButton' => false, + 'centerVertical'=>true + ]); + + + $expected = << + + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + public function testScrollable() + { + Modal::$counter = 0; + $out = Modal::widget([ + 'closeButton' => false, + 'scrollable'=>true + ]); + + + $expected = << + + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/NavBarTest.php b/tests/NavBarTest.php new file mode 100644 index 0000000..6bc65b6 --- /dev/null +++ b/tests/NavBarTest.php @@ -0,0 +1,129 @@ + 'My Company', + 'brandUrl' => '/', + 'options' => [ + 'class' => 'navbar-inverse navbar-static-top navbar-frontend', + ], + ]); + + $expected = << +
    +My Company + + +
    + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testBrandImage() + { + $out = NavBar::widget([ + 'brandImage' => '/images/test.jpg', + 'brandUrl' => '/', + ]); + + $this->assertContains('', $out); + } + + public function testBrandLink() + { + $out = NavBar::widget([ + 'brandLabel' => 'Yii Framework', + 'brandUrl' => false, + ]); + + $this->assertContains('Yii Framework', $out); + } + + public function testBrandSpan() + { + $out = NavBar::widget([ + 'brandLabel' => 'Yii Framework', + 'brandUrl' => null, + ]); + + $this->assertContains('Yii Framework', $out); + } + + /** + * @depends testRender + */ + public function testNavAndForm() { + + NavBar::$counter = 0; + + ob_start(); + NavBar::begin([ + 'brandLabel' => 'My Company', + 'brandUrl' => '/', + 'options' => [ + ], + ]); + echo Nav::widget([ + 'options' => [ + 'class' => ['mr-auto'] + ], + 'items' => [ + ['label' => 'Home', 'url' => '#'], + ['label' => 'Link', 'url' => '#'], + ['label' => 'Dropdown', 'items' => [ + ['label' => 'Action', 'url' => '#'], + ['label' => 'Another action', 'url' => '#'], + '-', + ['label' => 'Something else here', 'url' => '#'], + ]] + ] + ]); + echo << + + + +HTML; + NavBar::end(); + $out = ob_get_clean(); + + $expected = << + + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/NavTest.php b/tests/NavTest.php new file mode 100644 index 0000000..c21ced5 --- /dev/null +++ b/tests/NavTest.php @@ -0,0 +1,368 @@ +mockWebApplication([ + 'components' => [ + 'request' => [ + 'class' => 'yii\web\Request', + 'scriptUrl' => '/base/index.php', + 'hostInfo' => 'http://example.com/', + 'url' => '/base/index.php&r=site%2Fcurrent&id=42' + ], + 'urlManager' => [ + 'class' => 'yii\web\UrlManager', + 'baseUrl' => '/base', + 'scriptUrl' => '/base/index.php', + 'hostInfo' => 'http://example.com/', + ] + ], + ]); + } + + public function testIds() + { + Nav::$counter = 0; + $out = Nav::widget( + [ + 'items' => [ + [ + 'label' => 'Page1', + 'content' => 'Page1', + ], + [ + 'label' => 'Dropdown1', + 'items' => [ + ['label' => 'Page2', 'content' => 'Page2'], + ['label' => 'Page3', 'content' => 'Page3'], + ] + ], + [ + 'label' => 'Dropdown2', + 'visible' => false, + 'items' => [ + ['label' => 'Page4', 'content' => 'Page4'], + ['label' => 'Page5', 'content' => 'Page5'], + ] + ] + ] + ] + ); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testRenderDropdownWithDropdownOptions() + { + Nav::$counter = 0; + $out = Nav::widget( + [ + 'items' => [ + [ + 'label' => 'Page1', + 'content' => 'Page1', + ], + [ + 'label' => 'Dropdown1', + 'dropdownOptions' => ['class' => 'test', 'data-id' => 't1', 'id' => 'test1'], + 'items' => [ + ['label' => 'Page2', 'content' => 'Page2'], + ['label' => 'Page3', 'content' => 'Page3'], + ] + ], + [ + 'label' => 'Dropdown2', + 'visible' => false, + 'items' => [ + ['label' => 'Page4', 'content' => 'Page4'], + ['label' => 'Page5', 'content' => 'Page5'], + ] + ] + ] + ] + ); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testEmptyItems() + { + Nav::$counter = 0; + $out = Nav::widget([ + 'items' => [ + [ + 'label' => 'Page1', + 'items' => null, + ], + [ + 'label' => 'Dropdown1', + 'items' => [ + ['label' => 'Page2', 'content' => 'Page2'], + ['label' => 'Page3', 'content' => 'Page3'], + ], + ], + [ + 'label' => 'Page4', + 'items' => [], + ], + ], + ]); + + $expected = << + + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testActive() + { + $this->mockAction('site', 'users'); + + Nav::$counter = 0; + $out = Nav::widget([ + 'items' => [ + [ + 'label' => 'Main', + 'url' => ['site/index'], + ], + [ + 'label' => 'Admin', + 'items' => [ + ['label' => 'Users', 'url' => ['site/users']], + ['label' => 'Roles', 'url' => ['site/roles']], + ['label' => 'Statuses', 'url' => ['site/statuses']] + ], + ], + ], + ]); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + $this->removeMockedAction(); + } + + /** + * @see https://github.com/yiisoft/yii2-bootstrap/issues/162 + */ + public function testExplicitActive() + { + $this->mockAction('site', 'index'); + + Nav::$counter = 0; + $out = Nav::widget([ + 'activateItems' => false, + 'items' => [ + [ + 'label' => 'Item1', + 'active' => true, + ], + [ + 'label' => 'Item2', + 'url' => ['site/index'], + ], + ], + ]); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + $this->removeMockedAction(); + } + + /** + * @see https://github.com/yiisoft/yii2-bootstrap/issues/162 + */ + public function testImplicitActive() + { + $this->mockAction('site', 'index'); + + Nav::$counter = 0; + $out = Nav::widget([ + 'items' => [ + [ + 'label' => 'Item1', + 'active' => true, + ], + [ + 'label' => 'Item2', + 'url' => ['site/index'], + ], + ], + ]); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + $this->removeMockedAction(); + } + + /** + * @see https://github.com/yiisoft/yii2-bootstrap/issues/162 + */ + public function testExplicitActiveSubitems() + { + $this->mockAction('site', 'index'); + + Nav::$counter = 0; + $out = Nav::widget([ + 'activateItems' => false, + 'items' => [ + [ + 'label' => 'Item1', + ], + [ + 'label' => 'Item2', + 'items' => [ + ['label' => 'Page2', 'content' => 'Page2', 'url' => ['site/index']], + ['label' => 'Page3', 'content' => 'Page3', 'active' => true], + ], + ], + ], + ]); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + $this->removeMockedAction(); + } + + /** + * @see https://github.com/yiisoft/yii2-bootstrap/issues/162 + */ + public function testImplicitActiveSubitems() + { + $this->mockAction('site', 'index'); + + Nav::$counter = 0; + $out = Nav::widget([ + 'items' => [ + [ + 'label' => 'Item1', + ], + [ + 'label' => 'Item2', + 'items' => [ + ['label' => 'Page2', 'content' => 'Page2', 'url' => ['site/index']], + ['label' => 'Page3', 'content' => 'Page3', 'active' => true], + ], + ], + ], + ]); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + $this->removeMockedAction(); + } + + public function testDisabled() + { + $this->mockAction('site', 'index'); + + Nav::$counter = 0; + $out = Nav::widget([ + 'items' => [ + [ + 'label' => 'Item1', + 'disabled' => true + ], + [ + 'label' => 'Item2', + 'items' => [ + ['label' => 'Page2', 'content' => 'Page2', 'url' => ['site/index'], 'disabled' => true], + ['label' => 'Page3', 'content' => 'Page3', 'active' => true], + ], + ], + ], + ]); + + $expected = << + +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + $this->removeMockedAction(); + } + + /** + * @see https://github.com/yiisoft/yii2-bootstrap/issues/96 + * @see https://github.com/yiisoft/yii2-bootstrap/issues/157 + */ + public function testDeepActivateParents() + { + Nav::$counter = 0; + $out = Nav::widget([ + 'activateParents' => true, + 'items' => [ + [ + 'label' => 'Dropdown', + 'items' => [ + [ + 'label' => 'Sub-dropdown', + 'items' => [ + ['label' => 'Page', 'content' => 'Page', 'active' => true], + ], + ], + ], + ], + ], + ]); + + $expected = << +EXPECTED; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/ProgressTest.php b/tests/ProgressTest.php new file mode 100644 index 0000000..e762637 --- /dev/null +++ b/tests/ProgressTest.php @@ -0,0 +1,98 @@ + 'Progress', + 'percent' => 25, + 'barOptions' => ['class' => 'bg-warning'] + ]); + + $expected = << +
    Progress
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testRender() + { + Progress::$counter = 0; + $out = Progress::widget([ + 'bars' => [ + ['label' => 'Progress', 'percent' => 25] + ] + ]); + + $expected = << +
    Progress
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + /** + * @depends testRender + */ + public function testMultiple() + { + Progress::$counter = 0; + $out = Progress::widget([ + 'bars' => [ + ['label' => '', 'percent' => 15], + ['label' => '', 'percent' => 30, 'options' => ['class' => ['bg-success']]], + ['label' => '', 'percent' => 20, 'options' => ['class' => ['bg-info']]] + ] + ]); + + $expected = << +
    +
    +
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + /** + * @see https://github.com/yiisoft/yii2-bootstrap5/issues/121 + */ + public function testRussianLocaleRendering() + { + $this->mockWebApplication([ + 'language' => 'ru-RU', + 'sourceLanguage' => 'en-US', + ]); + + Progress::$counter = 0; + $out = Progress::widget([ + 'bars' => [ + ['label' => 'Progress', 'percent' => 25] + ] + ]); + + $expected = << +
    Progress
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/TabsTest.php b/tests/TabsTest.php new file mode 100644 index 0000000..ff84619 --- /dev/null +++ b/tests/TabsTest.php @@ -0,0 +1,408 @@ + [ + [ + 'label' => 'Page1', + 'content' => 'Page1', + ], + [ + 'label' => 'Page2', + 'content' => 'Page2', + ], + ] + ]); + + $this->assertContainsWithoutLE(' +
    Content 1
    +
    Content 2
    +HTML; + + $this->assertEquals($expected, $html); + } + + public function testHeaderOptions() + { + Tabs::$counter = 0; + $html = Tabs::widget([ + 'items' => [ + [ + 'label' => 'Tab 1', + 'content' => '
    Content 1
    ', + ], + [ + 'label' => 'Tab 2', + 'content' => '
    Content 2
    ', + 'headerOptions' => ['class' => 'col-6'], + ], + [ + 'label' => 'Link', + 'url' => 'http://www.example.com/', + 'headerOptions' => ['class' => 'col-3'], + ], + ], + 'options' => ['class' => 'row'], + 'headerOptions' => ['class' => 'col'], + ]); + + $expected = << + + +
    Content 1
    +
    Content 2
    +HTML; + + $this->assertEquals($expected, $html); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..e2eae23 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,122 @@ +assertEquals($expected, $actual); + } + + /** + * Asserting two strings equality ignoring line endings + * + * @param string $needle + * @param string $haystack + */ + public function assertContainsWithoutLE(string $needle, string $haystack) + { + $needle = str_replace("\r\n", "\n", $needle); + $haystack = str_replace("\r\n", "\n", $haystack); + + $this->assertContains($needle, $haystack); + } + + /** + * {@inheritDoc} + */ + protected function setUp() + { + parent::setUp(); + $this->mockWebApplication(); + } + + /** + * {@inheritDoc} + */ + protected function tearDown() + { + parent::tearDown(); + $this->destroyApplication(); + } + + /** + * @param array $config + * @param string $appClass + */ + protected function mockWebApplication(array $config = [], string $appClass = '\yii\web\Application') + { + new $appClass(ArrayHelper::merge([ + 'id' => 'testapp', + 'basePath' => __DIR__, + 'vendorPath' => dirname(__DIR__) . '/vendor', + 'aliases' => [ + '@bower' => '@vendor/bower-asset', + '@npm' => '@vendor/npm-asset', + ], + 'components' => [ + 'request' => [ + 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq', + 'scriptFile' => __DIR__ . '/index.php', + 'scriptUrl' => '/index.php', + ] + ] + ], $config)); + } + + /** + * Mocks controller action with parameters + * + * @param string $controllerId + * @param string $actionID + * @param string|null $moduleID + * @param array $params + */ + protected function mockAction(string $controllerId, string $actionID, string $moduleID = null, array $params = []) + { + Yii::$app->controller = $controller = new Controller($controllerId, Yii::$app); + $controller->actionParams = $params; + $controller->action = new Action($actionID, $controller); + + if ($moduleID !== null) { + $controller->module = new Module($moduleID); + } + } + + /** + * Removes controller + */ + protected function removeMockedAction() + { + Yii::$app->controller = null; + } + + /** + * Destroys application in Yii::$app by setting it to null. + */ + protected function destroyApplication() + { + Yii::$app = null; + Yii::$container = new Container(); + } +} diff --git a/tests/ToastTest.php b/tests/ToastTest.php new file mode 100644 index 0000000..9829a00 --- /dev/null +++ b/tests/ToastTest.php @@ -0,0 +1,125 @@ + ['class' => 'toast-body test', 'style' => ['text-align' => 'center']] + ]); + + $expected = << +
    + + +
    +
    + + +
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + /** + * @depends testBodyOptions + */ + public function testContainerOptions() + { + Toast::$counter = 0; + + ob_start(); + Toast::begin([ + 'title' => 'Toast title', + 'dateTime' => time() - 60 + ]); + echo 'Woohoo, you\'re reading this text in a toast!'; + Toast::end(); + $out = ob_get_clean(); + + $expected = << +
    +Toast title +a minute ago + +
    +
    +Woohoo, you're reading this text in a toast! + +
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testDateTimeOptions() + { + Toast::$counter = 0; + $out = Toast::widget([ + 'title' => 'Toast title', + 'dateTime' => time() - 60, + 'dateTimeOptions' => ['class' => ['toast-date-time'], 'style' => ['text-align' => 'right']] + ]); + + $expected = << +
    +Toast title +a minute ago + +
    +
    + + +
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } + + public function testTitleOptions() + { + Toast::$counter = 0; + $out = Toast::widget([ + 'title' => 'Toast title', + 'titleOptions' => ['tag' => 'h5', 'style' => ['text-align' => 'left']] + ]); + + $expected = << +
    +
    Toast title
    + +
    +
    + + +
    + +HTML; + + $this->assertEqualsWithoutLE($expected, $out); + } +} diff --git a/tests/ToggleButtonGroupTest.php b/tests/ToggleButtonGroupTest.php new file mode 100644 index 0000000..c8e53ce --- /dev/null +++ b/tests/ToggleButtonGroupTest.php @@ -0,0 +1,94 @@ + ToggleButtonGroup::TYPE_CHECKBOX, + 'model' => new ToggleButtonGroupTestModel(), + 'attribute' => 'value', + 'items' => [ + '1' => 'item 1', + '2' => 'item 2', + ], + ]); + + $expectedHtml = <<
    +
    +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + /** + * @depends testCheckbox + */ + public function testCheckboxChecked() { + Html::$counter = 0; + $html = ToggleButtonGroup::widget([ + 'type' => ToggleButtonGroup::TYPE_CHECKBOX, + 'model' => new ToggleButtonGroupTestModel(['value' => '2']), + 'attribute' => 'value', + 'items' => [ + '1' => 'item 1', + '2' => 'item 2', + ], + ]); + + $this->assertContains('', $html); + } + + public function testRadio() + { + Html::$counter = 0; + $html = ToggleButtonGroup::widget([ + 'type' => ToggleButtonGroup::TYPE_RADIO, + 'model' => new ToggleButtonGroupTestModel(), + 'attribute' => 'value', + 'items' => [ + '1' => 'item 1', + '2' => 'item 2', + ], + ]); + + $expectedHtml = <<
    +
    +HTML; + $this->assertEqualsWithoutLE($expectedHtml, $html); + } + + /** + * @depends testRadio + */ + public function testRadioChecked() { + Html::$counter = 0; + $html = ToggleButtonGroup::widget([ + 'type' => ToggleButtonGroup::TYPE_RADIO, + 'model' => new ToggleButtonGroupTestModel(['value' => '2']), + 'attribute' => 'value', + 'items' => [ + '1' => 'item 1', + '2' => 'item 2', + ], + ]); + + $this->assertContains('', $html); + } +} + +class ToggleButtonGroupTestModel extends Model +{ + public $value; +} diff --git a/tests/assets/.gitignore b/tests/assets/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/tests/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..dece628 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,14 @@ + + */ +class ExtendedActiveField extends ActiveField +{ + public ?array $horizontalCssClasses = [ + 'offset' => 'col-md-offset-4', + 'label' => 'col-md-4', + 'wrapper' => 'col-md-6', + 'error' => 'col-md-3', + 'hint' => 'col-md-3', + ]; +} diff --git a/tests/data/Singer.php b/tests/data/Singer.php new file mode 100644 index 0000000..fd8ad5e --- /dev/null +++ b/tests/data/Singer.php @@ -0,0 +1,27 @@ + + */ +class Singer extends Model +{ + public $firstName; + public $lastName; + public $test; + + public function rules() + { + return [ + [['lastName'], 'default', 'value' => 'Lennon'], + [['lastName'], 'required'], + [['underscore_style'], 'yii\captcha\CaptchaValidator'], + [['test'], 'required', 'when' => function($model) { return $model->firstName === 'cebe'; }], + ]; + } +} diff --git a/tests/data/User.php b/tests/data/User.php new file mode 100644 index 0000000..0e068c4 --- /dev/null +++ b/tests/data/User.php @@ -0,0 +1,41 @@ + + */ + +namespace yiiunit\extensions\bootstrap5\data; + + +use yii\base\Model; + +class User extends Model +{ + public $firstName; + public $lastName; + public $username; + public $password; + + /** + * {@inheritdoc} + */ + public function rules() + { + return [ + ['username', 'string', 'min' => 4], + ['password', 'string', 'min' => 8, 'max' => '20'], + [['username', 'password'], 'required'] + ]; + } + + /** + * {@inheritdoc} + */ + public function attributeHints() + { + return [ + 'username' => 'Your username must be at least 4 characters long', + 'password' => 'Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji.' + ]; + } +}