diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..042d009 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..773a14a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +* text=auto + +/.github export-ignore +/_docs export-ignore +/tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.scrutinizer.yml export-ignore +.travis.yml export-ignore +phpunit.xml export-ignore +CONTRIBUTING.md export-ignore diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..d62146a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +- Sidebar Version: #.#.# +- Laravel Version: #.#.# +- PHP Version: #.#.# + +### Description: + +### Steps To Reproduce: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c59329 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build/ +/vendor/ +/composer.phar +/composer.lock diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..282131b --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,35 @@ +filter: + excluded_paths: [tests/*, vendor/*] + +checks: + php: + code_rating: true + duplication: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true + +tools: + external_code_coverage: + timeout: 600 + runs: 3 + php_code_sniffer: + enabled: true + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + php_cpd: + enabled: true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..96b482d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: php + +sudo: false + +php: + - 5.6 + - 7.0 + - 7.1 + - nightly + +matrix: + allow_failures: + - php: nightly + +env: + - TESTBENCH_VERSION=3.4.* + +before_script: + - travis_retry composer self-update + - travis_retry composer require --prefer-source --no-interaction --dev "orchestra/testbench:${TESTBENCH_VERSION}" + +script: + - composer validate + - mkdir -p build/logs + - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover + +after_script: + - if [ "$TRAVIS_PHP_VERSION" != "nightly" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi + - if [ "$TRAVIS_PHP_VERSION" != "nightly" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..eebedc7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# CONTRIBUTING + +Contributions are welcome, and are accepted via pull requests. Please review these guidelines before submitting any pull requests. + +## Guidelines + + * Please follow the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) Coding Standard, PHP-FIG Naming Conventions and the [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) autoloading standard + * Ensure that the current tests pass, and if you've added something new, add the tests where relevant. + * Remember that we follow SemVer. If you are changing the behaviour, or the public api, you may need to update the docs. + * Send a coherent commit history, making sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + * You may also need to rebase to avoid merge conflicts. + +## Running Tests + +You will need an install of [Composer](https://getcomposer.org) before continuing. + +First, install the dependencies: + +```bash +$ composer install +``` + +Then run phpunit: + +```bash +$ vendor/bin/phpunit --coverage-text +``` + +If the test suite passes on your local machine you should be good to go. + +When you make a pull request, the tests will automatically be run again by [Travis CI](https://travis-ci.org/). diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..60d659a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 | ARCANEDEV - Sidebar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 9f4d7c6..ecf8f42 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -# Sidebar -Sidebar generator for ARCANESOFT's admin dashboard +# Sidebar [![Packagist License][badge_license]](LICENSE.md) [![For ARCANESOFT CMS][badge_arcanesoft]][link-arcanesoft-repo] + +[![Travis Status][badge_build]][link-travis] +[![Coverage Status][badge_coverage]][link-scrutinizer] +[![Scrutinizer Code Quality][badge_quality]][link-scrutinizer] +[![SensioLabs Insight][badge_insight]][link-insight] +[![Github Issues][badge_issues]][link-github-issues] + +[![Packagist][badge_package]][link-packagist] +[![Packagist Release][badge_release]][link-packagist] +[![Packagist Downloads][badge_downloads]][link-packagist] + +*By [ARCANEDEV©](http://www.arcanedev.net/)* + +Sidebar generator for ARCANESOFT's admin dashboard. + +[badge_license]: https://img.shields.io/packagist/l/arcanedev/sidebar.svg?style=flat-square +[badge_arcanesoft]: https://img.shields.io/badge/For-ARCANESOFT%20CMS-orange.svg?style=flat-square +[badge_build]: https://img.shields.io/travis/ARCANEDEV/Sidebar.svg?style=flat-square +[badge_coverage]: https://img.shields.io/scrutinizer/coverage/g/ARCANEDEV/Sidebar.svg?style=flat-square +[badge_quality]: https://img.shields.io/scrutinizer/g/ARCANEDEV/Sidebar.svg?style=flat-square +[badge_insight]: https://img.shields.io/sensiolabs/i/[id].svg?style=flat-square +[badge_issues]: https://img.shields.io/github/issues/ARCANEDEV/Sidebar.svg?style=flat-square +[badge_package]: https://img.shields.io/badge/package-arcanedev/sidebar-blue.svg?style=flat-square +[badge_release]: https://img.shields.io/packagist/v/arcanedev/sidebar.svg?style=flat-square +[badge_downloads]: https://img.shields.io/packagist/dt/arcanedev/sidebar.svg?style=flat-square + +[link-author]: https://github.com/arcanedev-maroc +[link-arcanesoft-repo]: https://github.com/ARCANESOFT/ARCANESOFT +[link-github-issues]: https://github.com/ARCANEDEV/Sidebar/issues +[link-contributors]: https://github.com/ARCANEDEV/Sidebar/graphs/contributors +[link-packagist]: https://packagist.org/packages/arcanedev/sidebar +[link-travis]: https://travis-ci.org/ARCANEDEV/Sidebar +[link-scrutinizer]: https://scrutinizer-ci.com/g/ARCANEDEV/Sidebar/?branch=master +[link-insight]: https://insight.sensiolabs.com/projects/[id] diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f172dc5 --- /dev/null +++ b/composer.json @@ -0,0 +1,44 @@ +{ + "name": "arcanesoft/sidebar", + "description": "Sidebar generator for ARCANESOFT's admin dashboard", + "keywords": ["arcanedev", "arcanesoft", "laravel", "sidebar", "navigation"], + "homepage": "https://github.com/ARCANESOFT/Sidebar", + "authors": [ + { + "name": "ARCANEDEV", + "email": "arcanedev.maroc@gmail.com", + "homepage": "https://github.com/arcanedev-maroc", + "role": "Developer" + } + ], + "type": "library", + "license": "MIT", + "require": { + "php": ">=5.6.4", + "arcanedev/support": "~4.0", + "arcanesoft/contracts": "~2.0" + }, + "require-dev": { + "phpunit/phpcov": "~3.0", + "phpunit/phpunit": "~5.0" + }, + "autoload": { + "psr-4": { + "Arcanesoft\\Sidebar\\": "src/" + }, + "files": ["helpers.php"] + }, + "autoload-dev": { + "psr-4": { + "Arcanesoft\\Sidebar\\Tests\\": "tests/" + } + }, + "scripts": { + "testbench": "composer require --dev \"orchestra/testbench=~3.0\"" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/helpers.php b/helpers.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/helpers.php @@ -0,0 +1 @@ + + + + + ./tests + + + + + ./src + + + + + + + + + + + diff --git a/src/Contracts/Manager.php b/src/Contracts/Manager.php new file mode 100644 index 0000000..2f65442 --- /dev/null +++ b/src/Contracts/Manager.php @@ -0,0 +1,104 @@ + + */ +interface Manager +{ + /* ----------------------------------------------------------------- + | Getters & Setters + | ----------------------------------------------------------------- + */ + /** + * Set the view name. + * + * @param string $view + * + * @return self + */ + public function setView($view); + + /** + * Get the current item name. + * + * @return string + */ + public function getCurrent(); + + /** + * Set the current item name. + * + * @param string $currentName + * + * @return $this + */ + public function setCurrent($currentName); + + /** + * Get the sidebar items. + * + * @return \Arcanesoft\Sidebar\Entities\ItemCollection + */ + public function getItems(); + + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + /** + * Add a routed item. + * + * @param string $name + * @param string $title + * @param string $route + * @param array $parameters + * @param string|null $icon + * + * @return self + */ + public function addRouteItem($name, $title, $route, array $parameters = [], $icon = null); + + /** + * Add an item. + * + * @param string $name + * @param string $title + * @param string $url + * @param string|null $icon + * + * @return self + */ + public function addItem($name, $title, $url = '#', $icon = null); + + /** + * Add an item from array. + * + * @param array $array + * + * @return self + */ + public function add(array $array); + + /** + * Render the sidebar. + * + * @param string|null $view + * + * @return string + */ + public function render($view = null); + + /* ----------------------------------------------------------------- + | Check Methods + | ----------------------------------------------------------------- + */ + /** + * Check if the sidebar has items. + * + * @return bool + */ + public function hasItems(); +} diff --git a/src/Entities/Item.php b/src/Entities/Item.php new file mode 100644 index 0000000..b5b6cf1 --- /dev/null +++ b/src/Entities/Item.php @@ -0,0 +1,499 @@ + + */ +class Item implements Arrayable, Jsonable, JsonSerializable +{ + /* ----------------------------------------------------------------- + | Properties + | ----------------------------------------------------------------- + */ + /** + * The item name. + * + * @var string + */ + protected $name; + + /** + * The item title. + * + * @var string + */ + protected $title; + + /** + * The item url. + * + * @var string + */ + protected $url; + + /** + * The item icon. + * + * @var string + */ + protected $icon; + + /** + * The item active state. + * + * @var bool + */ + protected $active = false; + + /** + * The authenticated user. + * + * @var \Arcanesoft\Contracts\Auth\Models\User + */ + protected $user; + + /** + * The item roles. + * + * @var array + */ + protected $roles = []; + + /** + * The item permissions. + * + * @var array + */ + protected $permissions = []; + + /** + * The item children (sub-items). + * + * @var \Arcanesoft\Sidebar\Entities\ItemCollection + */ + protected $children; + + /* ----------------------------------------------------------------- + | Constructor + | ----------------------------------------------------------------- + */ + /** + * Item constructor. + * + * @param string $name + * @param string $title + * @param string $url + * @param string|null $icon + */ + public function __construct($name, $title, $url, $icon = null) + { + $this->name = $name; + $this->title = $title; + $this->url = $url; + $this->icon = $icon; + $this->active = false; + $this->children = new ItemCollection; + } + + /* ----------------------------------------------------------------- + | Getters & Setters + | ----------------------------------------------------------------- + */ + /** + * Get the item name. + * + * @return string + */ + public function name() + { + return $this->name; + } + + /** + * Get the item title. + * + * @return string + */ + public function title() + { + /** @var \Illuminate\Translation\Translator $trans */ + $trans = trans(); + + return $trans->has($this->title) ? $trans->get($this->title) : $this->title; + } + + /** + * Set the title. + * + * @param string $title + * + * @return self + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * Get the item url. + * + * @return string + */ + public function url() + { + return $this->url; + } + + /** + * Get the item icon. + * + * @return string|null + */ + public function icon() + { + return $this->icon; + } + + /** + * Set the item icon. + * + * @param string $icon + * + * @return self + */ + public function setIcon($icon) + { + $this->icon = $icon; + + return $this; + } + + /** + * Set the current name. + * + * @param string $name + * + * @return self + */ + public function setCurrent($name) + { + $this->children->setCurrent($name); + $this->active = ($this->name === $name || $this->children->hasActiveItem()); + + return $this; + } + + /** + * Set the authenticated user. + * + * @param \Arcanesoft\Contracts\Auth\Models\User $user + * + * @return self + */ + public function setUser(User $user = null) + { + if ( ! is_null($user)) + $this->user = $user; + + return $this; + } + + /** + * Get the roles. + * + * @return array + */ + public function getRoles() + { + return $this->roles; + } + + /** + * Set the roles. + * + * @param array $roles + * + * @return self + */ + public function setRoles(array $roles) + { + $this->roles = $roles; + + return $this; + } + + /** + * Get the permissions. + * + * @return array + */ + public function getPermissions() + { + return $this->permissions; + } + + /** + * Set the permissions. + * + * @param array $permissions + * + * @return self + */ + public function setPermissions(array $permissions) + { + $this->permissions = $permissions; + + return $this; + } + + /** + * Get the sub-items. + * + * @return \Arcanesoft\Sidebar\Entities\ItemCollection + */ + public function children() + { + return $this->children; + } + + /** + * Get the active class. + * + * @param string $class + * + * @return string + */ + public function activeClass($class = 'active') + { + return $this->isActive() ? $class : ''; + } + + /** + * Get the sub-items class. + * + * @param string $class + * + * @return string + */ + public function childrenClass($class = 'treeview') + { + return $this->hasChildren() ? $class : ''; + } + + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + /** + * Make the item. + * + * @param string $name + * @param string $title + * @param string $url + * @param string|null $icon + * + * @return self + */ + public static function make($name, $title, $url, $icon = null) + { + return new self($name, $title, $url, $icon); + } + + /** + * Make a Sidebar item from array. + * + * @param array $array + * @param \Arcanesoft\Contracts\Auth\Models\User $user + * + * @return self + */ + public static function makeFromArray(array $array, User $user = null) + { + $item = self::make( + $array['name'], + $array['title'], + self::getUrlFromArray($array), + Arr::get($array, 'icon', null) + ); + + $item->setUser($user); + $item->setRoles(Arr::get($array, 'roles', [])); + $item->setPermissions(Arr::get($array, 'permissions', [])); + $item->addChildren(Arr::get($array, 'children', [])); + + return $item; + } + + /** + * Get url from array. + * + * @param array $array + * + * @return string + */ + private static function getUrlFromArray(array $array) + { + if (Arr::has($array, 'route')) + return route(Arr::get($array, 'route')); + + return Arr::get($array, 'url', '#'); + } + + /** + * Add children to the parent. + * + * @param array $children + * + * @return self + */ + public function addChildren(array $children) + { + foreach ($children as $child) { + $this->addChild($child); + } + + return $this; + } + + /** + * Add a sub-item to the parent. + * + * @param array $child + * + * @return self + */ + public function addChild(array $child) + { + $item = self::makeFromArray($child, $this->user); + + if ($item->allowed()) + $this->children->push($item); + + return $this; + } + + /* ----------------------------------------------------------------- + | Check Methods + | ----------------------------------------------------------------- + */ + /** + * Check if the item is active one. + * + * @return bool + */ + public function isActive() + { + return $this->active; + } + + /** + * Check if the item has children. + * + * @return bool + */ + public function hasChildren() + { + return ! $this->children->isEmpty(); + } + + /** + * Check the user is allowed to see this item. + * + * @return bool + */ + public function allowed() + { + if (is_null($this->user) || ( ! $this->hasRoles() && ! $this->hasPermissions())) + return true; + + if ($this->user->isAdmin()) + return true; + + foreach ($this->roles as $roleSlug) { + if ($this->user->hasRoleSlug($roleSlug)) + return true; + } + + foreach ($this->permissions as $permissionSlug) { + if ($this->user->may($permissionSlug)) + return true; + } + + return false; + } + + /** + * Check if the item has roles. + * + * @return bool + */ + public function hasRoles() + { + return ! empty($this->roles); + } + + /** + * Check if the item has permissions. + * + * @return bool + */ + public function hasPermissions() + { + return ! empty($this->permissions); + } + + /* ----------------------------------------------------------------- + | Other Methods + | ----------------------------------------------------------------- + */ + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'name' => $this->name(), + 'title' => $this->title(), + 'url' => $this->url(), + 'icon' => $this->icon(), + 'active' => $this->isActive(), + 'roles' => $this->roles, + 'permissions' => $this->permissions, + 'children' => $this->children->toArray(), + ]; + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/src/Entities/ItemCollection.php b/src/Entities/ItemCollection.php new file mode 100644 index 0000000..e17d67c --- /dev/null +++ b/src/Entities/ItemCollection.php @@ -0,0 +1,46 @@ + + */ +class ItemCollection extends Collection +{ + /* ----------------------------------------------------------------- + | Getters & Setters + | ----------------------------------------------------------------- + */ + /** + * Set the current name to the items collection. + * + * @param string $currentName + * + * @return self + */ + public function setCurrent($currentName) + { + return $this->transform(function (Item $item) use ($currentName) { + return $item->setCurrent($currentName); + }); + } + + /* ----------------------------------------------------------------- + | Check Methods + | ----------------------------------------------------------------- + */ + /** + * Check if the items collection has an active one. + * + * @return bool + */ + public function hasActiveItem() + { + return ! $this->filter(function (Item $item) { + return $item->isActive(); + })->isEmpty(); + } +} diff --git a/src/Facades/Sidebar.php b/src/Facades/Sidebar.php new file mode 100644 index 0000000..a13b505 --- /dev/null +++ b/src/Facades/Sidebar.php @@ -0,0 +1,20 @@ + + */ +class Sidebar extends Facade +{ + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() { return ManagerContract::class; } +} diff --git a/src/Manager.php b/src/Manager.php new file mode 100644 index 0000000..f0c8707 --- /dev/null +++ b/src/Manager.php @@ -0,0 +1,217 @@ + + */ +class Manager implements ManagerContract +{ + /* ----------------------------------------------------------------- + | Properties + | ----------------------------------------------------------------- + */ + /** + * The view name. + * + * @var string + */ + protected $view = ''; + + /** + * The current name. + * + * @var string + */ + protected $currentName; + + /** + * The sidebar items collection. + * + * @var \Arcanesoft\Sidebar\Entities\ItemCollection + */ + protected $items; + + /** + * The authenticated user. + * + * @var \Arcanesoft\Contracts\Auth\Models\User + */ + protected $user; + + /* ----------------------------------------------------------------- + | Constructor + | ----------------------------------------------------------------- + */ + /** + * Manager constructor. + */ + public function __construct() + { + $this->items = new ItemCollection; + $this->setAuthenticatedUser(); + } + + /* ----------------------------------------------------------------- + | Getters & Setters + | ----------------------------------------------------------------- + */ + /** + * Set the view name. + * + * @param string $view + * + * @return self + */ + public function setView($view) + { + if ( ! is_null($view)) + $this->view = $view; + + return $this; + } + + /** + * Get the current item name. + * + * @return string + */ + public function getCurrent() + { + return $this->currentName; + } + + /** + * Set the current item name. + * + * @param string $currentName + * + * @return $this + */ + public function setCurrent($currentName) + { + $this->currentName = $currentName; + + return $this; + } + + /** + * Get the sidebar items. + * + * @return \Arcanesoft\Sidebar\Entities\ItemCollection + */ + public function getItems() + { + return $this->items; + } + + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + /** + * Add a routed item. + * + * @param string $name + * @param string $title + * @param string $route + * @param array $parameters + * @param string|null $icon + * + * @return self + */ + public function addRouteItem($name, $title, $route, array $parameters = [], $icon = null) + { + return $this->addItem($name, $title, route($route, $parameters), $icon); + } + + /** + * Add an item. + * + * @param string $name + * @param string $title + * @param string $url + * @param string|null $icon + * + * @return self + */ + public function addItem($name, $title, $url = '#', $icon = null) + { + return $this->add(compact('name', 'title', 'url', 'icon')); + } + + /** + * Add an item from array. + * + * @param array $array + * + * @return self + */ + public function add(array $array) + { + $item = Item::makeFromArray($array, $this->user); + + if ($item->allowed()) $this->items->push($item); + + return $this; + } + + /** + * Render the sidebar. + * + * @param string|null $view + * + * @return string + */ + public function render($view = null) + { + $this->syncCurrentName()->setView($view); + + return view($this->view, ['sidebarItems' => $this->items])->render(); + } + + /* ----------------------------------------------------------------- + | Check Methods + | ----------------------------------------------------------------- + */ + /** + * Check if the sidebar has items. + * + * @return bool + */ + public function hasItems() + { + return ! $this->items->isEmpty(); + } + + /* ----------------------------------------------------------------- + | Other Methods + | ----------------------------------------------------------------- + */ + /** + * Sync the current name wih the sidebar items. + * + * @return self + */ + private function syncCurrentName() + { + $this->items->setCurrent($this->currentName); + + return $this; + } + + /** + * Get the authenticated user. + */ + private function setAuthenticatedUser() + { + if (auth()->guest()) return; + + $this->user = auth()->user()->load(['roles', 'roles.permissions']); + } +} diff --git a/src/SidebarServiceProvider.php b/src/SidebarServiceProvider.php new file mode 100644 index 0000000..7b9b3f8 --- /dev/null +++ b/src/SidebarServiceProvider.php @@ -0,0 +1,64 @@ + + */ +class SidebarServiceProvider extends PackageServiceProvider +{ + /* ----------------------------------------------------------------- + | Properties + | ----------------------------------------------------------------- + */ + /** + * Vendor name. + * + * @var string + */ + protected $vendor = 'arcanesoft'; + + /** + * Package name. + * + * @var string + */ + protected $package = 'sidebar'; + + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + /** + * Register the service provider. + */ + public function register() + { + parent::register(); + + $this->singleton(Contracts\Manager::class, Manager::class); + } + + /** + * Boot the service provider. + */ + public function boot() + { + parent::boot(); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + Contracts\Manager::class, + ]; + } +} diff --git a/tests/Entities/ItemTest.php b/tests/Entities/ItemTest.php new file mode 100644 index 0000000..d7cb26b --- /dev/null +++ b/tests/Entities/ItemTest.php @@ -0,0 +1,185 @@ + + */ +class ItemTest extends TestCase +{ + /* ----------------------------------------------------------------- + | Tests + | ----------------------------------------------------------------- + */ + /** @test */ + public function it_can_be_instantiated() + { + $item = $this->createItem($name = 'home', $title = 'Home', $this->baseUrl); + + $this->assertSame($name, $item->name()); + $this->assertSame($title, $item->title()); + $this->assertSame($this->baseUrl, $item->url()); + $this->assertSame('fa fa-fw fa-home', $item->icon()); + $this->assertFalse($item->isActive()); + + $this->assertFalse($item->hasRoles()); + $this->assertEmpty($item->getRoles()); + $this->assertFalse($item->hasPermissions()); + $this->assertEmpty($item->getPermissions()); + + $this->assertFalse($item->hasChildren()); + + $children = $item->children(); + + $this->assertInstanceOf(\Arcanesoft\Sidebar\Entities\ItemCollection::class, $children); + $this->assertCount(0, $children); + } + + /** @test */ + public function it_can_make_item() + { + $item = Item::make($name = 'home', $title = 'Home', $this->baseUrl, $icon = 'fa fa-fw fa-home'); + + $this->assertSame($name, $item->name()); + $this->assertSame($title, $item->title()); + $this->assertSame($this->baseUrl, $item->url()); + $this->assertSame($icon, $item->icon()); + $this->assertFalse($item->isActive()); + + $this->assertFalse($item->hasRoles()); + $this->assertEmpty($item->getRoles()); + $this->assertFalse($item->hasPermissions()); + $this->assertEmpty($item->getPermissions()); + + $this->assertFalse($item->hasChildren()); + + $children = $item->children(); + + $this->assertInstanceOf(\Arcanesoft\Sidebar\Entities\ItemCollection::class, $children); + $this->assertCount(0, $children); + } + + /** @test */ + public function it_can_set_current_name_for_active_class() + { + $item = $this->createItem('home', 'Home', $this->baseUrl); + + $this->assertFalse($item->isActive()); + $this->assertEmpty($item->activeClass()); + + $item->setCurrent('home'); + + $this->assertTrue($item->isActive()); + $this->assertSame('active', $item->activeClass()); + $this->assertSame('is-active', $item->activeClass('is-active')); + } + + /** @test */ + public function it_can_convert_to_array() + { + $item = $this->createItem('home', 'Home', $this->baseUrl); + + $this->assertInstanceOf(\Illuminate\Contracts\Support\Arrayable::class, $item); + + $expected = [ + 'name' => 'home', + 'title' => 'Home', + 'url' => 'http://localhost', + 'icon' => 'fa fa-fw fa-home', + 'active' => false, + 'roles' => [], + 'permissions' => [], + 'children' => [], + ]; + + $this->assertSame($expected, $item->toArray()); + } + + /** @test */ + public function it_can_convert_to_json() + { + $item = $this->createItem('home', 'Home', $this->baseUrl); + + $this->assertInstanceOf(\Illuminate\Contracts\Support\Jsonable::class, $item); + $this->assertInstanceOf(\JsonSerializable::class, $item); + + $expected = json_encode($item->toArray(), 0); + $this->assertJson($json = $item->toJson()); + $this->assertSame($expected, $json); + + $expected = json_encode($item, 0); + + $this->assertJson($json = $item->toJson()); + $this->assertSame($expected, $json); + } + + /** @test */ + public function it_can_translate_titles() + { + $item = $this->createItem('home', 'sidebar::titles.home', $this->baseUrl); + + $this->assertSame('Home', $item->title()); + + $this->app->setLocale('fr'); + + $this->assertSame('Accueil', $item->title()); + } + + /** @test */ + public function it_can_add_children_without_auth_checks() + { + $item = $this->createItem('seo', 'SEO', '#'); + + $this->assertFalse($item->hasChildren()); + $this->assertCount(0, $item->children()); + $this->assertEmpty($item->childrenClass()); + + $item->addChildren([ + [ + 'title' => 'Statistics', + 'name' => 'seo-dashboard', + 'route' => 'sidebar::seo.stats', + 'icon' => 'fa fa-fw fa-bar-chart', + ],[ + 'title' => 'Pages', + 'name' => 'seo-pages', + 'route' => 'sidebar::seo.pages', + 'icon' => 'fa fa-fw fa-files-o', + ], + ]); + + $this->assertTrue($item->hasChildren()); + $this->assertCount(2, $item->children()); + $this->assertSame('treeview', $item->childrenClass()); + $this->assertSame('sub-items', $item->childrenClass('sub-items')); + } + + /* ----------------------------------------------------------------- + | Other Methods + | ----------------------------------------------------------------- + */ + /** + * Create a sidebar item. + * + * @param string $name + * @param string $title + * @param string $url + * @param array $roles + * @param array $permissions + * + * @return \Arcanesoft\Sidebar\Entities\Item + */ + private function createItem($name, $title, $url, array $roles = [], array $permissions = []) + { + $item = new Item($name, $title, $url, $icon = 'fa fa-fw fa-home'); + + $item->setRoles($roles); + $item->setPermissions($permissions); + + return $item; + } +} diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php new file mode 100644 index 0000000..3bbdbc6 --- /dev/null +++ b/tests/ManagerTest.php @@ -0,0 +1,80 @@ + + */ +class ManagerTest extends TestCase +{ + /* ----------------------------------------------------------------- + | Properties + | ----------------------------------------------------------------- + */ + /** @var \Arcanesoft\Sidebar\Contracts\Manager */ + private $manager; + + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + public function setUp() + { + parent::setUp(); + + $this->manager = $this->app->make(\Arcanesoft\Sidebar\Contracts\Manager::class); + } + + public function tearDown() + { + unset($this->manager); + + parent::tearDown(); + } + + /* ----------------------------------------------------------------- + | Tests + | ----------------------------------------------------------------- + */ + /** @test */ + public function it_can_be_instantiated() + { + $expectations = [ + \Arcanesoft\Sidebar\Contracts\Manager::class, + \Arcanesoft\Sidebar\Manager::class, + ]; + + foreach ($expectations as $expected) { + $this->assertInstanceOf($expected, $this->manager); + } + + $this->assertFalse($this->manager->hasItems()); + $this->assertCount(0, $this->manager->getItems()); + } + + /** @test */ + public function it_can_add_one_item() + { + $this->manager->addItem( + $name = 'auth', + $title = 'Authorization', + $url = 'http://localhot/dashboard/auth', + $icon = 'fa fa-fw fa-users' + ); + + $this->assertTrue($this->manager->hasItems()); + + $items = $this->manager->getItems(); + + $this->assertCount(1, $items); + + /** @var \Arcanesoft\Sidebar\Entities\Item $item */ + $item = $items->first(); + + $this->assertSame($title, $item->title()); + $this->assertSame($icon, $item->icon()); + $this->assertEmpty($item->getRoles()); + $this->assertEmpty($item->getPermissions()); + } +} diff --git a/tests/SidebarServiceProviderTest.php b/tests/SidebarServiceProviderTest.php new file mode 100644 index 0000000..59f4ef7 --- /dev/null +++ b/tests/SidebarServiceProviderTest.php @@ -0,0 +1,64 @@ + + */ +class SidebarServiceProviderTest extends TestCase +{ + /* ----------------------------------------------------------------- + | Properties + | ----------------------------------------------------------------- + */ + /** @var \Arcanesoft\Sidebar\SidebarServiceProvider */ + private $provider; + + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + public function setUp() + { + parent::setUp(); + + $this->provider = $this->app->getProvider(\Arcanesoft\Sidebar\SidebarServiceProvider::class); + } + + public function tearDown() + { + unset($this->provider); + + parent::tearDown(); + } + + /* ----------------------------------------------------------------- + | Tests + | ----------------------------------------------------------------- + */ + /** @test */ + public function it_can_be_instantiated() + { + $expectations = [ + \Illuminate\Support\ServiceProvider::class, + \Arcanedev\Support\ServiceProvider::class, + \Arcanedev\Support\PackageServiceProvider::class, + \Arcanesoft\Sidebar\SidebarServiceProvider::class, + ]; + + foreach ($expectations as $expected) { + $this->assertInstanceOf($expected, $this->provider); + } + } + + /** @test */ + public function it_can_provides() + { + $expected = [ + \Arcanesoft\Sidebar\Contracts\Manager::class, + ]; + + $this->assertSame($expected, $this->provider->provides()); + } +} diff --git a/tests/Stubs/TestServiceProvider.php b/tests/Stubs/TestServiceProvider.php new file mode 100644 index 0000000..81b6c9f --- /dev/null +++ b/tests/Stubs/TestServiceProvider.php @@ -0,0 +1,48 @@ + + */ +class TestServiceProvider extends ServiceProvider +{ + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + public function boot() + { + parent::boot(); + + $this->loadTranslationsFrom(__DIR__.'/../fixtures/resources/lang', 'sidebar'); + + $this->registerRoutes($this->app['router']); + } + + /* ----------------------------------------------------------------- + | Other Functions + | ----------------------------------------------------------------- + */ + /** + * Register the routes for tests. + * + * @param \Illuminate\Routing\Router $router + */ + private function registerRoutes(Router $router) + { + $router->group(['as' => 'sidebar::seo.'], function (Router $router) { + $router->get('/', function() { + return 'SEO stats'; + })->name('stats'); // sidebar::seo.stats + + $router->get('pages', function() { + return 'SEO pages'; + })->name('pages'); // sidebar::seo.pages + }); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..f478867 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,53 @@ + + */ +abstract class TestCase extends \Orchestra\Testbench\TestCase +{ + /* ----------------------------------------------------------------- + | Main Methods + | ----------------------------------------------------------------- + */ + /** + * Get package providers. + * + * @param \Illuminate\Foundation\Application $app + * + * @return array + */ + protected function getPackageProviders($app) + { + return [ + \Arcanesoft\Sidebar\Tests\Stubs\TestServiceProvider::class, + \Arcanesoft\Sidebar\SidebarServiceProvider::class, + ]; + } + + /** + * Get package aliases. + * + * @param \Illuminate\Foundation\Application $app + * + * @return array + */ + protected function getPackageAliases($app) + { + return [ + 'Sidebar' => \Arcanesoft\Sidebar\Facades\Sidebar::class, + ]; + } + + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + */ + protected function getEnvironmentSetUp($app) + { + // + } +} diff --git a/tests/fixtures/resources/lang/en/titles.php b/tests/fixtures/resources/lang/en/titles.php new file mode 100644 index 0000000..d1691aa --- /dev/null +++ b/tests/fixtures/resources/lang/en/titles.php @@ -0,0 +1,7 @@ + 'Home', + 'auth' => 'Authorizations', + 'settings' => 'Settings', +]; diff --git a/tests/fixtures/resources/lang/fr/titles.php b/tests/fixtures/resources/lang/fr/titles.php new file mode 100644 index 0000000..e62b06d --- /dev/null +++ b/tests/fixtures/resources/lang/fr/titles.php @@ -0,0 +1,7 @@ + 'Accueil', + 'auth' => 'Autorisations', + 'settings' => 'Réglages', +];