jqh пре 6 година
родитељ
комит
42cf846647

+ 2 - 1
src/Admin.php

@@ -13,6 +13,7 @@ use Dcat\Admin\Support\Helper;
 use Dcat\Admin\Traits\HasAssets;
 use Dcat\Admin\Layout\Menu;
 use Dcat\Admin\Layout\Navbar;
+use Illuminate\Auth\GuardHelpers;
 use Illuminate\Contracts\Auth\Authenticatable;
 use Illuminate\Contracts\Auth\Guard;
 use Illuminate\Database\Eloquent\Model;
@@ -126,7 +127,7 @@ class Admin
     /**
      * Attempt to get the guard from the local cache.
      *
-     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
+     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard|GuardHelpers
      */
     public static function guard()
     {

+ 2 - 1
src/Controllers/AuthController.php

@@ -6,6 +6,7 @@ use Dcat\Admin\Models\Repositories\Administrator;
 use Dcat\Admin\Admin;
 use Dcat\Admin\Form;
 use Dcat\Admin\Layout\Content;
+use Illuminate\Auth\GuardHelpers;
 use Illuminate\Http\Request;
 use Illuminate\Routing\Controller;
 use Illuminate\Support\Facades\Auth;
@@ -255,7 +256,7 @@ class AuthController extends Controller
     /**
      * Get the guard to be used during authentication.
      *
-     * @return \Illuminate\Contracts\Auth\StatefulGuard
+     * @return \Illuminate\Contracts\Auth\StatefulGuard|GuardHelpers
      */
     protected function guard()
     {

+ 1 - 1
src/Form.php

@@ -757,7 +757,7 @@ class Form implements Renderable
         }
 
         // 非ajax请求
-        $status = (int) ($options['status_code'] ?? 200);
+        $status = (int) ($options['status_code'] ?? 302);
 
         admin_alert($message);
 

+ 180 - 0
tests/Controllers/UserController.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace Tests\Controllers;
+
+use App\Http\Controllers\Controller;
+use Dcat\Admin\Admin;
+use Dcat\Admin\Form;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Layout\Content;
+use Dcat\Admin\Controllers\HasResourceActions;
+use Tests\Models\Tag;
+use Tests\Repositories\User;
+
+class UserController extends Controller
+{
+    use HasResourceActions;
+
+    /**
+     * Index interface.
+     *
+     * @return Content
+     */
+    public function index(Content $content)
+    {
+        $content->header('All users');
+        $content->description('description');
+
+        return $content->body($this->grid());
+    }
+
+    /**
+     * Edit interface.
+     *
+     * @param $id
+     *
+     * @return Content
+     */
+    public function edit(Content $content, $id)
+    {
+        $content->header('Edit user');
+        $content->description('description');
+
+        $content->body($this->form()->edit($id));
+
+        return $content;
+    }
+
+    /**
+     * Create interface.
+     *
+     * @return Content
+     */
+    public function create(Content $content)
+    {
+        $content->header('Create user');
+
+        return $content->body($this->form());
+    }
+
+    /**
+     * Make a grid builder.
+     *
+     * @return Grid
+     */
+    protected function grid()
+    {
+        $grid = new Grid(new User());
+
+        $grid->model()->with('tags');
+
+        $grid->id('ID')->sortable();
+
+        $grid->username();
+        $grid->email();
+        $grid->mobile();
+        $grid->full_name();
+        $grid->avatar()->display(function ($avatar) {
+            return "<img src='{$avatar}' />";
+        });
+        $grid->column('profile.postcode', 'Post code');
+//        $grid->profile()->address();
+        $grid->column('profile.address');
+//        $grid->position('Position');
+        $grid->column('profile.color');
+//        $grid->profile()->start_at('开始时间');
+        $grid->column('profile.start_at', '开始时间');
+//        $grid->profile()->end_at('结束时间');
+        $grid->column('profile.end_at', '结束时间');
+
+        $grid->column('column1_not_in_table')->display(function () {
+            return 'full name:'.$this->full_name;
+        });
+
+        $grid->column('column2_not_in_table')->display(function () {
+            return $this->email.'#'.$this->profile['color'];
+        });
+
+        $grid->tags()->display(function ($tags) {
+            $tags = collect($tags)->map(function ($tag) {
+                return "<code>{$tag['name']}</code>";
+            })->toArray();
+
+            return implode('', $tags);
+        });
+
+        $grid->created_at();
+        $grid->updated_at();
+
+        $grid->showExporter();
+
+        $grid->filter(function (Grid\Filter $filter) {
+            $filter->equal('id');
+            $filter->like('username');
+            $filter->like('email');
+            $filter->like('profile.postcode');
+            $filter->between('profile.start_at')->datetime();
+            $filter->between('profile.end_at')->datetime();
+        });
+
+        $grid->actions(function (Grid\Displayers\Actions $actions) {
+            if ($actions->getKey() % 2 == 0) {
+                $actions->append('<a href="/" class="btn btn-xs btn-danger">detail</a>');
+            }
+        });
+
+        return $grid;
+    }
+
+    /**
+     * Make a form builder.
+     *
+     * @return Form
+     */
+    protected function form()
+    {
+        Form::extend('map', Form\Field\Map::class);
+        Form::extend('editor', Form\Field\Editor::class);
+
+        $form = new Form(new User());
+
+        $form->disableDeleteButton();
+
+        $form->display('id', 'ID');
+        $form->text('username');
+        $form->email('email')->rules('required');
+        $form->mobile('mobile');
+        $form->image('avatar')->help('上传头像', 'fa-image');
+        $form->ignore(['password_confirmation']);
+        $form->password('password')->rules('confirmed');
+        $form->password('password_confirmation');
+
+        $form->divider();
+
+        $form->text('profile.first_name');
+        $form->text('profile.last_name');
+        $form->text('profile.postcode')->help('Please input your postcode');
+        $form->textarea('profile.address')->rows(15);
+        $form->map('profile.latitude', 'profile.longitude', 'Position');
+        $form->color('profile.color');
+        $form->datetime('profile.start_at');
+        $form->datetime('profile.end_at');
+
+//        $tags = Tag::all()->pluck('name', 'id');
+//        print_r($tags);die;
+
+        $form->multipleSelect('tags', 'Tags')->options(Tag::all()->pluck('name', 'id'))->customFormat(function ($value) {
+            if (! $value) {
+                return [];
+            }
+            return array_column($value, 'id');
+        }); //->rules('max:10|min:3');
+
+        $form->display('created_at', 'Created At');
+        $form->display('updated_at', 'Updated At');
+
+        $form->html('<a html-field>html...</a>');
+
+        return $form;
+    }
+}

+ 25 - 0
tests/CreatesApplication.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Tests;
+
+use Illuminate\Contracts\Console\Kernel;
+
+trait CreatesApplication
+{
+    /**
+     * Creates the application.
+     *
+     * @return \Illuminate\Foundation\Application
+     */
+    public function createApplication()
+    {
+        $app = require __DIR__.'/../bootstrap/app.php';
+
+        $app->make(Kernel::class)->bootstrap();
+
+        $app->register('Dcat\Admin\AdminServiceProvider');
+
+        return $app;
+
+    }
+}

+ 61 - 0
tests/Feature/AuthTest.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+
+class AuthTest extends TestCase
+{
+    public function testLoginPage()
+    {
+        $this->visit('admin/auth/login')
+            ->see('login');
+    }
+
+    public function testVisitWithoutLogin()
+    {
+        $this->visit('admin')
+            ->dontSeeIsAuthenticated('admin')
+            ->seePageIs('admin/auth/login');
+    }
+
+    public function testLogin()
+    {
+        $credentials = ['username' => 'admin', 'password' => 'admin'];
+
+        $this->visit('admin/auth/login')
+            ->see('login')
+            ->submitForm('Login', $credentials)
+            ->see('dashboard')
+            ->seeCredentials($credentials, 'admin')
+            ->seeIsAuthenticated('admin')
+            ->seePageIs('admin')
+            ->see('Dashboard')
+            ->see('Description...')
+
+            ->see('Environment')
+            ->see('PHP version')
+            ->see('Laravel version')
+
+            ->see('Extensions')
+
+            ->see('Dependencies')
+            ->see('php')
+            ->see('laravel/framework');
+
+        $this
+            ->see('<span>Admin</span>')
+            ->see('<span>Users</span>')
+            ->see('<span>Roles</span>')
+            ->see('<span>Permission</span>')
+            ->see('<span>Operation log</span>')
+            ->see('<span>Menu</span>');
+    }
+
+    public function testLogout()
+    {
+        $this->visit('admin/auth/logout')
+            ->seePageIs('admin/auth/login')
+            ->dontSeeIsAuthenticated('admin');
+    }
+}

+ 51 - 0
tests/Feature/IndexTest.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+
+class IndexTest extends TestCase
+{
+    protected $login = true;
+
+    public function testIndex()
+    {
+        $this->visit('admin/')
+            ->see('Dashboard')
+            ->see('Description...')
+
+            ->see('Environment')
+            ->see('PHP version')
+            ->see('Laravel version')
+
+            ->see('Extensions')
+
+            ->see('Dependencies')
+            ->see('php')
+            ->see('laravel/framework');
+    }
+
+    public function testClickMenu()
+    {
+        $this->visit('admin/')
+            ->click('Users')
+            ->seePageis('admin/auth/users')
+            ->click('Roles')
+            ->seePageis('admin/auth/roles')
+            ->click('Permission')
+            ->seePageis('admin/auth/permissions')
+            ->click('Menu')
+            ->seePageis('admin/auth/menu')
+            ->click('Operation log')
+            ->seePageis('admin/auth/logs')
+            ->click('Extensions')
+            ->seePageis('admin/helpers/extensions')
+            ->click('Scaffold')
+            ->seePageis('admin/helpers/scaffold')
+            ->click('Routes')
+            ->seePageis('admin/helpers/routes')
+            ->click('Icons')
+            ->seePageis('admin/helpers/icons');
+
+    }
+}

+ 37 - 0
tests/Feature/InstallTest.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+
+class InstallTest extends TestCase
+{
+    public function testInstalledDirectories()
+    {
+        $this->assertFileExists(admin_path());
+
+        $this->assertFileExists(admin_path('Controllers'));
+
+        $this->assertFileExists(admin_path('routes.php'));
+
+        $this->assertFileExists(admin_path('bootstrap.php'));
+
+        $this->assertFileExists(admin_path('Controllers/HomeController.php'));
+
+        $this->assertFileExists(admin_path('Controllers/AuthController.php'));
+
+        $this->assertFileExists(admin_path('Controllers/ExampleController.php'));
+
+        $this->assertFileExists(config_path('admin.php'));
+
+        $this->assertFileExists(public_path('vendor/dcat-admin'));
+
+        $this->assertFileExists(database_path('migrations/2016_01_04_173148_create_admin_tables.php'));
+
+        $this->assertFileExists(resource_path('lang/en/admin.php'));
+
+        $this->assertFileExists(resource_path('lang/zh-CN/admin.php'));
+
+        $this->assertFileExists(resource_path('lang/zh-CN/global.php'));
+    }
+}

+ 63 - 0
tests/Feature/MenuTest.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+
+use Dcat\Admin\Models\Menu;
+
+class MenuTest extends TestCase
+{
+    protected $login = true;
+
+    public function testMenuIndex()
+    {
+        $this->visit('admin/auth/menu')
+            ->see('Menu')
+            ->see('Index')
+            ->see('Auth')
+            ->see('Users')
+            ->see('Roles')
+            ->see('Permission')
+            ->see('Menu')
+            ->see('Operation log');
+    }
+
+    public function testAddMenu()
+    {
+        $item = ['parent_id' => '0', 'title' => 'Test', 'uri' => 'test', 'icon' => 'fa-user'];
+
+        $this->visit('admin/auth/menu')
+            ->seePageIs('admin/auth/menu')
+            ->see('Menu')
+            ->submitForm('Submit', $item)
+            ->seePageIs('admin/auth/menu')
+            ->seeInDatabase(config('admin.database.menu_table'), $item)
+            ->assertEquals(8, Menu::count());
+    }
+
+    public function testDeleteMenu()
+    {
+        $this->delete('admin/auth/menu/8')
+            ->assertEquals(7, Menu::count());
+    }
+
+    public function testEditMenu()
+    {
+        $this->visit('admin/auth/menu/1/edit')
+            ->see('Menu')
+            ->submitForm('Submit', ['title' => 'blablabla'])
+            ->seePageIs('admin/auth/menu')
+            ->seeInDatabase(config('admin.database.menu_table'), ['title' => 'blablabla'])
+            ->assertEquals(7, Menu::count());
+    }
+
+    public function testEditMenuParent()
+    {
+        $this->expectException(\Laravel\BrowserKitTesting\HttpException::class);
+
+        $this->visit('admin/auth/menu/5/edit')
+            ->see('Menu')
+            ->submitForm('Submit', ['parent_id' => 5]);
+    }
+}

+ 81 - 0
tests/Feature/OperationLogTest.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+use Dcat\Admin\Models\OperationLog;
+
+class OperationLogTest extends TestCase
+{
+    protected $login = true;
+
+    public function testOperationLogIndex()
+    {
+        $this->visit('admin/auth/logs')
+            ->see('Operation log')
+            ->see('List')
+            ->see('GET')
+            ->see('admin/auth/logs');
+    }
+
+    public function testGenerateLogs()
+    {
+        $table = config('admin.database.operation_log_table');
+
+        $this->visit('admin/auth/menu')
+            ->seePageIs('admin/auth/menu')
+            ->visit('admin/auth/users')
+            ->seePageIs('admin/auth/users')
+            ->visit('admin/auth/permissions')
+            ->seePageIs('admin/auth/permissions')
+            ->visit('admin/auth/roles')
+            ->seePageIs('admin/auth/roles')
+            ->visit('admin/auth/logs')
+            ->seePageIs('admin/auth/logs')
+            ->seeInDatabase($table, ['path' => 'admin/auth/menu', 'method' => 'GET'])
+            ->seeInDatabase($table, ['path' => 'admin/auth/users', 'method' => 'GET'])
+            ->seeInDatabase($table, ['path' => 'admin/auth/permissions', 'method' => 'GET'])
+            ->seeInDatabase($table, ['path' => 'admin/auth/roles', 'method' => 'GET']);
+
+        $this->assertEquals(4, OperationLog::count());
+    }
+
+    public function testDeleteLogs()
+    {
+        $table = config('admin.database.operation_log_table');
+
+        $this->visit('admin/auth/logs')
+            ->seePageIs('admin/auth/logs')
+            ->assertEquals(0, OperationLog::count());
+
+        $this->visit('admin/auth/users');
+
+        $this->seeInDatabase($table, ['path' => 'admin/auth/users', 'method' => 'GET']);
+
+        $this->delete('admin/auth/logs/1')
+            ->assertEquals(0, OperationLog::count());
+    }
+
+    public function testDeleteMultipleLogs()
+    {
+        $table = config('admin.database.operation_log_table');
+
+        $this->visit('admin/auth/menu')
+            ->visit('admin/auth/users')
+            ->visit('admin/auth/permissions')
+            ->visit('admin/auth/roles')
+            ->seeInDatabase($table, ['path' => 'admin/auth/menu', 'method' => 'GET'])
+            ->seeInDatabase($table, ['path' => 'admin/auth/users', 'method' => 'GET'])
+            ->seeInDatabase($table, ['path' => 'admin/auth/permissions', 'method' => 'GET'])
+            ->seeInDatabase($table, ['path' => 'admin/auth/roles', 'method' => 'GET'])
+            ->assertEquals(4, OperationLog::count());
+
+        $this->delete('admin/auth/logs/1,2,3,4')
+            ->notSeeInDatabase($table, ['path' => 'admin/auth/menu', 'method' => 'GET'])
+            ->notSeeInDatabase($table, ['path' => 'admin/auth/users', 'method' => 'GET'])
+            ->notSeeInDatabase($table, ['path' => 'admin/auth/permissions', 'method' => 'GET'])
+            ->notSeeInDatabase($table, ['path' => 'admin/auth/roles', 'method' => 'GET'])
+
+            ->assertEquals(0, OperationLog::count());
+    }
+}

+ 137 - 0
tests/Feature/PermissionsTest.php

@@ -0,0 +1,137 @@
+<?php
+
+namespace Tests\Feature;
+
+use Dcat\Admin\Models\Administrator;
+use Dcat\Admin\Models\Permission;
+use Dcat\Admin\Models\Role;
+use Tests\TestCase;
+
+class PermissionsTest extends TestCase
+{
+    protected $login = true;
+
+    public function testPermissionsIndex()
+    {
+        $this->assertTrue(Administrator::first()->isAdministrator());
+
+        $this->visit('admin/auth/permissions')
+            ->see('Permissions');
+    }
+
+    public function testAddAndDeletePermissions()
+    {
+        $this->visit('admin/auth/permissions/create')
+            ->see('Permissions')
+            ->submitForm('Submit', ['slug' => 'can-edit', 'name' => 'Can edit', 'http_path' => ['users/1/edit'], 'http_method' => ['GET']])
+            ->seePageIs('admin/auth/permissions')
+            ->visit('admin/auth/permissions/create')
+            ->see('Permissions')
+            ->submitForm('Submit', ['slug' => 'can-delete', 'name' => 'Can delete', 'http_path' => ['users/1'], 'http_method' => ['DELETE']])
+            ->seePageIs('admin/auth/permissions')
+            ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-edit', 'name' => 'Can edit', 'http_path' => 'users/1/edit', 'http_method' => 'GET'])
+            ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-delete', 'name' => 'Can delete', 'http_path' => 'users/1', 'http_method' => 'DELETE'])
+            ->assertEquals(8, Permission::count());
+
+        $this->assertTrue(Administrator::first()->can('can-edit'));
+        $this->assertTrue(Administrator::first()->can('can-delete'));
+
+        $this->delete('admin/auth/permissions/7')
+            ->assertEquals(7, Permission::count());
+
+        $this->delete('admin/auth/permissions/8')
+            ->assertEquals(6, Permission::count());
+    }
+
+    public function testAddPermissionToRole()
+    {
+        $this->visit('admin/auth/permissions/create')
+            ->see('Permissions')
+            ->submitForm('Submit', ['slug' => 'can-create', 'name' => 'Can Create', 'http_path' => ['users/create'], 'http_method' => ['GET']])
+            ->seePageIs('admin/auth/permissions');
+
+        $this->assertEquals(7, Permission::count());
+
+        $this->visit('admin/auth/roles/1/edit')
+            ->see('Edit')
+            ->submitForm('Submit', ['permissions' => 1])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.role_permissions_table'), ['role_id' => 1, 'permission_id' => 1]);
+    }
+
+
+    public function testPermissionThroughRole()
+    {
+        $user = [
+            'username'              => 'Test',
+            'name'                  => 'Name',
+            'password'              => '123456',
+            'password_confirmation' => '123456',
+        ];
+
+        // 1.add a user
+        $this->visit('admin/auth/users/create')
+            ->see('Create')
+            ->submitForm('Submit', $user)
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.users_table'), ['username' => 'Test']);
+
+        $this->assertFalse(Administrator::find(2)->isAdministrator());
+
+        // 2.add a role
+        $this->visit('admin/auth/roles/create')
+            ->see('Roles')
+            ->submitForm('Submit', ['slug' => 'developer', 'name' => 'Developer...'])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.roles_table'), ['slug' => 'developer', 'name' => 'Developer...'])
+            ->assertEquals(2, Role::count());
+
+        $this->assertFalse(Administrator::find(2)->isRole('developer'));
+
+        // 3.assign role to user
+        $this->visit('admin/auth/users/2/edit')
+            ->see('Edit')
+            ->submitForm('Submit', ['roles' => [2]])
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.role_users_table'), ['user_id' => 2, 'role_id' => 2]);
+
+        $this->assertTrue(Administrator::find(2)->isRole('developer'));
+
+        //  4.add a permission
+        $this->visit('admin/auth/permissions/create')
+            ->see('Permissions')
+            ->submitForm('Submit', ['slug' => 'can-remove', 'name' => 'Can Remove', 'http_path' => ['users/*'], 'http_method' => ['DELETE']])
+            ->seePageIs('admin/auth/permissions');
+
+        $this->assertEquals(7, Permission::count());
+
+        $this->assertTrue(Administrator::find(2)->cannot('can-remove'));
+
+        // 5.assign permission to role
+        $this->visit('admin/auth/roles/2/edit')
+            ->see('Edit')
+            ->submitForm('Submit', ['permissions' => 7])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.role_permissions_table'), ['role_id' => 2, 'permission_id' => 7]);
+
+        $this->assertTrue(Administrator::find(2)->can('can-remove'));
+    }
+
+    public function testEditPermission()
+    {
+        $this->visit('admin/auth/permissions/create')
+            ->see('Permissions')
+            ->submitForm('Submit', ['slug' => 'can-edit', 'name' => 'Can edit', 'http_path' => ['users/1/edit'], 'http_method' => ['GET']])
+            ->seePageIs('admin/auth/permissions')
+            ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-edit'])
+            ->seeInDatabase(config('admin.database.permissions_table'), ['name' => 'Can edit'])
+            ->assertEquals(7, Permission::count());
+
+        $this->visit('admin/auth/permissions/1/edit')
+            ->see('Permissions')
+            ->submitForm('Submit', ['slug' => 'can-delete'])
+            ->seePageIs('admin/auth/permissions')
+            ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-delete'])
+            ->assertEquals(7, Permission::count());
+    }
+}

+ 103 - 0
tests/Feature/RolesTest.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+use Dcat\Admin\Models\Role;
+use Dcat\Admin\Models\Administrator;
+
+class RolesTest extends TestCase
+{
+    protected $login = true;
+
+    public function testRolesIndex()
+    {
+        $this->visit('admin/auth/roles')
+            ->see('Roles')
+            ->see('administrator');
+    }
+
+    public function testAddRole()
+    {
+        $this->visit('admin/auth/roles/create')
+            ->see('Roles')
+            ->submitForm('Submit', ['slug' => 'developer', 'name' => 'Developer...'])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.roles_table'), ['slug' => 'developer', 'name' => 'Developer...'])
+            ->assertEquals(2, Role::count());
+    }
+
+    public function testAddRoleToUser()
+    {
+        $user = [
+            'username'              => 'Test',
+            'name'                  => 'Name',
+            'password'              => '123456',
+            'password_confirmation' => '123456',
+
+        ];
+
+        $this->visit('admin/auth/users/create')
+            ->see('Create')
+            ->submitForm('Submit', $user)
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.users_table'), ['username' => 'Test']);
+
+        $this->assertEquals(1, Role::count());
+
+        $this->visit('admin/auth/roles/create')
+            ->see('Roles')
+            ->submitForm('Submit', ['slug' => 'developer', 'name' => 'Developer...'])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.roles_table'), ['slug' => 'developer', 'name' => 'Developer...'])
+            ->assertEquals(2, Role::count());
+
+        $this->assertFalse(Administrator::find(2)->isRole('developer'));
+
+        $this->visit('admin/auth/users/2/edit')
+            ->see('Edit')
+            ->submitForm('Submit', ['roles' => [2]])
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.role_users_table'), ['user_id' => 2, 'role_id' => 2]);
+
+        $this->assertTrue(Administrator::find(2)->isRole('developer'));
+
+        $this->assertFalse(Administrator::find(2)->inRoles(['editor', 'operator']));
+        $this->assertTrue(Administrator::find(2)->inRoles(['developer', 'operator', 'editor']));
+    }
+
+    public function testDeleteRole()
+    {
+        $this->assertEquals(1, Role::count());
+
+        $this->visit('admin/auth/roles/create')
+            ->see('Roles')
+            ->submitForm('Submit', ['slug' => 'developer', 'name' => 'Developer...'])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.roles_table'), ['slug' => 'developer', 'name' => 'Developer...']);
+//            ->assertEquals(2, Role::count());
+
+        $this->delete('admin/auth/roles/2');
+        $this->assertEquals(1, Role::count());
+
+//        $this->delete('admin/auth/roles/1');
+//        $this->assertEquals(0, Role::count());
+    }
+
+    public function testEditRole()
+    {
+        $this->visit('admin/auth/roles/create')
+            ->see('Roles')
+            ->submitForm('Submit', ['slug' => 'developer', 'name' => 'Developer...'])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.roles_table'), ['slug' => 'developer', 'name' => 'Developer...'])
+            ->assertEquals(2, Role::count());
+
+        $this->visit('admin/auth/roles/2/edit')
+            ->see('Roles')
+            ->submitForm('Submit', ['name' => 'blablabla'])
+            ->seePageIs('admin/auth/roles')
+            ->seeInDatabase(config('admin.database.roles_table'), ['name' => 'blablabla'])
+            ->assertEquals(2, Role::count());
+    }
+}

+ 199 - 0
tests/Feature/UserFormTest.php

@@ -0,0 +1,199 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+use Dcat\Admin\Models\Administrator;
+use Tests\Models\User as UserModel;
+
+class UserFormTest extends TestCase
+{
+    protected $login = true;
+
+    public function testCreatePage()
+    {
+        $this->visit('admin/users/create')
+            ->seeElement('input[type=text][name=username]')
+            ->seeElement('input[type=email][name=email]')
+            ->seeElement('input[type=text][name=mobile]')
+//            ->seeElement('input[type=file][name=avatar]')
+            ->seeElement('hr')
+            ->seeElement("input[type=text][name='profile[first_name]']")
+            ->seeElement("input[type=text][name='profile[last_name]']")
+            ->seeElement("input[type=text][name='profile[postcode]']")
+            ->seeElement("textarea[name='profile[address]'][rows=15]")
+            ->seeElement("input[type=hidden][name='profile[latitude]']")
+            ->seeElement("input[type=hidden][name='profile[longitude]']")
+            ->seeElement("input[type=text][name='profile[color]']")
+            ->seeElement("input[type=text][name='profile[start_at]']")
+            ->seeElement("input[type=text][name='profile[end_at]']")
+            ->seeElement('span[class=help-block] i[class*=fa-info-circle]')
+            ->seeInElement('span[class=help-block]', 'Please input your postcode')
+            ->seeElement('span[class=help-block] i[class*=fa-image]')
+            ->seeInElement('span[class=help-block]', '上传头像')
+            ->seeElement("select[name='tags[]'][multiple=multiple]")
+            ->seeInElement('a[html-field]', 'html...');
+    }
+
+    public function testSubmitForm()
+    {
+        $data = [
+            'username'              => 'John Doe',
+            'email'                 => 'hello@world.com',
+            'mobile'                => '13421234123',
+            'password'              => '123456',
+            'password_confirmation' => '123456',
+            //"avatar"   => "test.jpg",
+            'profile' => [
+                'first_name' => 'John',
+                'last_name'  => 'Doe',
+                'postcode'   => '123456',
+                'address'    => 'Jinshajiang RD',
+                'latitude'   => '131.2123123456',
+                'longitude'  => '21.342123456',
+                'color'      => '#ffffff',
+                'start_at'   => date('Y-m-d H:i:s', time()),
+                'end_at'     => date('Y-m-d H:i:s', time()),
+            ],
+        ];
+
+        $this->visit('admin/users/create')
+//            ->attach(__DIR__.'/assets/test.jpg', 'avatar')
+
+            ->submitForm('Submit', $data);
+
+        $this->seePageIs('admin/users')
+            ->seeInElement('td', 1)
+            ->seeInElement('td', $data['username'])
+            ->seeInElement('td', $data['email'])
+            ->seeInElement('td', $data['mobile'])
+            ->seeInElement('td', "{$data['profile']['first_name']} {$data['profile']['last_name']}")
+            ->seeElement('td img')
+            ->seeInElement('td', $data['profile']['postcode'])
+            ->seeInElement('td', $data['profile']['address'])
+//            ->seeInElement('td', "{$data['profile']['latitude']} {$data['profile']['longitude']}")
+            ->seeInElement('td', $data['profile']['color'])
+            ->seeInElement('td', $data['profile']['start_at'])
+            ->seeInElement('td', $data['profile']['end_at']);
+
+        $this->assertCount(1, UserModel::all());
+
+        $this->seeInDatabase('test_users', ['username' => $data['username']]);
+        $this->seeInDatabase('test_users', ['email' => $data['email']]);
+        $this->seeInDatabase('test_users', ['mobile' => $data['mobile']]);
+        $this->seeInDatabase('test_users', ['password' => $data['password']]);
+
+        $this->seeInDatabase('test_user_profiles', ['first_name' => $data['profile']['first_name']]);
+        $this->seeInDatabase('test_user_profiles', ['last_name' => $data['profile']['last_name']]);
+        $this->seeInDatabase('test_user_profiles', ['postcode' => $data['profile']['postcode']]);
+        $this->seeInDatabase('test_user_profiles', ['address' => $data['profile']['address']]);
+        $this->seeInDatabase('test_user_profiles', ['latitude' => $data['profile']['latitude']]);
+        $this->seeInDatabase('test_user_profiles', ['longitude' => $data['profile']['longitude']]);
+        $this->seeInDatabase('test_user_profiles', ['color' => $data['profile']['color']]);
+        $this->seeInDatabase('test_user_profiles', ['start_at' => $data['profile']['start_at']]);
+        $this->seeInDatabase('test_user_profiles', ['end_at' => $data['profile']['end_at']]);
+
+//        $avatar = UserModel::first()->avatar;
+
+//        $this->assertFileExists(public_path('uploads/'.$avatar));
+    }
+
+    protected function seedsTable($count = 100)
+    {
+        factory(\Tests\Models\User::class, $count)
+            ->create()
+            ->each(function ($u) {
+                $u->profile()->save(factory(\Tests\Models\Profile::class)->make());
+                $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make());
+            });
+    }
+
+    public function testEditForm()
+    {
+        $this->seedsTable(10);
+
+        $id = mt_rand(1, 10);
+
+        $user = UserModel::with('profile')->find($id);
+
+        $this->visit("admin/users/$id/edit")
+            ->seeElement("input[type=text][name=username][value='{$user->username}']")
+            ->seeElement("input[type=email][name=email][value='{$user->email}']")
+            ->seeElement("input[type=text][name=mobile][value='{$user->mobile}']")
+            ->seeElement('hr')
+            ->seeElement("input[type=text][name='profile[first_name]'][value='{$user->profile->first_name}']")
+            ->seeElement("input[type=text][name='profile[last_name]'][value='{$user->profile->last_name}']")
+            ->seeElement("input[type=text][name='profile[postcode]'][value='{$user->profile->postcode}']")
+            ->seeInElement("textarea[name='profile[address]']", $user->profile->address)
+//            ->seeElement("input[type=hidden][name='profile[latitude]'][value='{$user->profile->latitude}']")
+//            ->seeElement("input[type=hidden][name='profile[longitude]'][value='{$user->profile->longitude}']")
+            ->seeElement("input[type=text][name='profile[color]'][value='{$user->profile->color}']")
+            ->seeElement("input[type=text][name='profile[start_at]'][value='{$user->profile->start_at}']")
+            ->seeElement("input[type=text][name='profile[end_at]'][value='{$user->profile->end_at}']")
+            ->seeElement("select[name='tags[]'][multiple=multiple]");
+
+        $this->assertCount(50, $this->crawler()->filter("select[name='tags[]'] option"));
+        $this->assertCount(5, $this->crawler()->filter("select[name='tags[]'] option[selected]"));
+    }
+
+    public function testUpdateForm()
+    {
+        $this->seedsTable(10);
+
+        $id = mt_rand(1, 10);
+
+        $this->visit("admin/users/$id/edit")
+            ->type('hello world', 'username')
+            ->type('123', 'password')
+            ->type('123', 'password_confirmation')
+            ->press('Submit')
+            ->seePageIs('admin/users')
+            ->seeInDatabase('test_users', ['username' => 'hello world']);
+
+        $user = UserModel::with('profile')->find($id);
+
+        $this->assertEquals($user->username, 'hello world');
+    }
+
+    public function testUpdateFormWithRule()
+    {
+        $this->seedsTable(10);
+
+        $id = mt_rand(1, 10);
+
+        $this->visit("admin/users/$id/edit")
+            ->type('', 'email')
+            ->press('Submit')
+            ->seePageIs("admin/users/$id/edit")
+            ->see('The email field is required');
+
+        $this->type('xxaxx', 'email')
+            ->press('Submit')
+            ->seePageIs("admin/users/$id/edit")
+            ->see('The email must be a valid email address.');
+
+        $this->visit("admin/users/$id/edit")
+            ->type('123', 'password')
+            ->type('1234', 'password_confirmation')
+            ->press('Submit')
+            ->seePageIs("admin/users/$id/edit")
+            ->see('The Password confirmation does not match.');
+
+        $this->type('xx@xx.xx', 'email')
+            ->type('123', 'password')
+            ->type('123', 'password_confirmation')
+            ->press('Submit')
+            ->seePageIs('admin/users')
+            ->seeInDatabase('test_users', ['email' => 'xx@xx.xx']);
+    }
+
+
+    public function testFormFooter()
+    {
+        $this->seedsTable(1);
+
+        $this->visit('admin/users/1/edit')
+            ->seeElement('input[type=checkbox][value=1]')
+            ->seeElement('input[type=checkbox][value=3]');
+    }
+}

+ 224 - 0
tests/Feature/UserGridTest.php

@@ -0,0 +1,224 @@
+<?php
+
+namespace Tests\Feature;
+
+use Tests\TestCase;
+use Tests\Models\Profile as ProfileModel;
+use Tests\Models\User as UserModel;
+
+class UserGridTest extends TestCase
+{
+    protected $login = true;
+
+    public function testIndexPage()
+    {
+        $this->visit('admin/users')
+            ->see('All users')
+            ->seeInElement('tr th', 'Username')
+            ->seeInElement('tr th', 'Email')
+            ->seeInElement('tr th', 'Mobile')
+            ->seeInElement('tr th', 'Full name')
+            ->seeInElement('tr th', 'Avatar')
+            ->seeInElement('tr th', 'Post code')
+            ->seeInElement('tr th', 'Address')
+//            ->seeInElement('tr th', 'Position')
+            ->seeInElement('tr th', 'Color')
+            ->seeInElement('tr th', '开始时间')
+            ->seeInElement('tr th', '结束时间')
+            ->seeInElement('tr th', 'Color')
+            ->seeInElement('tr th', 'Created at')
+            ->seeInElement('tr th', 'Updated at');
+
+        $action = url('/admin/users');
+
+        $this->seeElement("form[action='$action'][method=get]")
+//            ->seeElement("form[action='$action'][method=get] input[name=id]")
+            ->seeElement("form[action='$action'][method=get] input[name=username]")
+            ->seeElement("form[action='$action'][method=get] input[name=email]")
+            ->seeElement("form[action='$action'][method=get] input[name='profile[start_at][start]']")
+            ->seeElement("form[action='$action'][method=get] input[name='profile[start_at][end]']")
+            ->seeElement("form[action='$action'][method=get] input[name='profile[end_at][start]']")
+            ->seeElement("form[action='$action'][method=get] input[name='profile[end_at][end]']");
+
+        $this->seeInElement('a[href="http://localhost:8000/admin/users?_export_=all"]', 'All')
+            ->seeInElement('a[href="http://localhost:8000/admin/users/create"]', 'New');
+    }
+
+    protected function seedsTable($count = 100)
+    {
+        factory(\Tests\Models\User::class, $count)
+            ->create()
+            ->each(function ($u) {
+                $u->profile()->save(factory(\Tests\Models\Profile::class)->make());
+                $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make());
+            });
+    }
+
+    public function testGridWithData()
+    {
+        $this->seedsTable();
+
+        $this->visit('admin/users')
+            ->see('All users');
+
+        $this->assertCount(100, UserModel::all());
+        $this->assertCount(100, ProfileModel::all());
+    }
+
+    public function testGridPagination()
+    {
+        $this->seedsTable(65);
+
+        $this->visit('admin/users')
+            ->see('All users');
+
+        $this->visit('admin/users?page=2');
+        $this->assertCount(20, $this->crawler()->filter('td a i[class*=fa-ellipsis-v]'));
+
+        $this->visit('admin/users?page=3');
+        $this->assertCount(20, $this->crawler()->filter('td a i[class*=fa-ellipsis-v]'));
+
+        $this->visit('admin/users?page=4');
+        $this->assertCount(5, $this->crawler()->filter('td a i[class*=fa-ellipsis-v]'));
+
+        $this->click(1)->seePageIs('admin/users?page=1');
+        $this->assertCount(20, $this->crawler()->filter('td a i[class*=fa-ellipsis-v]'));
+    }
+
+    public function testEqualFilter()
+    {
+        $this->seedsTable(50);
+
+        $this->visit('admin/users')
+            ->see('All users');
+
+        $this->assertCount(50, UserModel::all());
+        $this->assertCount(50, ProfileModel::all());
+
+        $id = mt_rand(1, 50);
+
+        $user = UserModel::find($id);
+
+        $this->visit('admin/users?id='.$id)
+            ->seeInElement('td', $user->username)
+            ->seeInElement('td', $user->email)
+            ->seeInElement('td', $user->mobile)
+//            ->seeElement("img[src='{$user->avatar}']")
+            ->seeInElement('td', "{$user->profile->first_name} {$user->profile->last_name}")
+            ->seeInElement('td', $user->postcode)
+            ->seeInElement('td', $user->address)
+//            ->seeInElement('td', "{$user->profile->latitude} {$user->profile->longitude}")
+            ->seeInElement('td', $user->color)
+            ->seeInElement('td', $user->start_at)
+            ->seeInElement('td', $user->end_at);
+    }
+
+    public function testLikeFilter()
+    {
+        $this->seedsTable(50);
+
+        $this->visit('admin/users')
+            ->see('All users');
+
+        $this->assertCount(50, UserModel::all());
+        $this->assertCount(50, ProfileModel::all());
+
+        $users = UserModel::where('username', 'like', '%mi%')->get();
+
+        $this->visit('admin/users?username=mi');
+
+        $this->assertEquals($this->crawler()->filter('td a i[class*=fa-ellipsis-v]')->count(), count($users->toArray()));
+
+        foreach ($users as $user) {
+            $this->seeInElement('td', $user->username);
+        }
+    }
+
+    public function testFilterRelation()
+    {
+        $this->seedsTable(20);
+
+        $user = UserModel::with('profile')->find(mt_rand(1, 20));
+
+        $this->visit('admin/users?email='.$user->email)
+            ->seeInElement('td', $user->username)
+            ->seeInElement('td', $user->email)
+            ->seeInElement('td', $user->mobile)
+//            ->seeElement("img[src='{$user->avatar}']")
+            ->seeInElement('td', "{$user->profile->first_name} {$user->profile->last_name}")
+            ->seeInElement('td', $user->postcode)
+            ->seeInElement('td', $user->address)
+//            ->seeInElement('td', "{$user->profile->latitude} {$user->profile->longitude}")
+            ->seeInElement('td', $user->color)
+            ->seeInElement('td', $user->start_at)
+            ->seeInElement('td', $user->end_at);
+    }
+
+    public function testDisplayCallback()
+    {
+        $this->seedsTable(1);
+
+        $user = UserModel::with('profile')->find(1);
+
+        $this->visit('admin/users')
+            ->seeInElement('th', 'Column1 not in table')
+            ->seeInElement('th', 'Column2 not in table')
+            ->seeInElement('td', "full name:{$user->profile->first_name} {$user->profile->last_name}")
+            ->seeInElement('td', "{$user->email}#{$user->profile->color}");
+    }
+
+    public function testHasManyRelation()
+    {
+        factory(\Tests\Models\User::class, 10)
+            ->create()
+            ->each(function ($u) {
+                $u->profile()->save(factory(\Tests\Models\Profile::class)->make());
+                $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make());
+            });
+
+        $this->visit('admin/users')
+            ->seeElement('td code');
+
+        $this->assertCount(50, $this->crawler()->filter('td code'));
+    }
+
+    public function testGridActions()
+    {
+        $this->seedsTable(15);
+
+        $this->visit('admin/users');
+
+        $this->assertCount(15, $this->crawler()->filter('td a i[class*=fa-ellipsis-v]'));
+    }
+
+    public function testGridRows()
+    {
+        $this->seedsTable(10);
+
+        $this->visit('admin/users')
+            ->seeInElement('td a[class*=btn]', 'detail');
+
+        $this->assertCount(5, $this->crawler()->filter('td a[class*=btn]'));
+    }
+
+    public function testGridPerPage()
+    {
+        $this->seedsTable(98);
+
+        $this->visit('admin/users')
+            ->seeElement('select[class*=per-page][name=per-page]')
+            ->seeInElement('select option', 10)
+            ->seeInElement('select option[selected]', 20)
+            ->seeInElement('select option', 30)
+            ->seeInElement('select option', 50)
+            ->seeInElement('select option', 100);
+
+        $this->assertEquals('http://localhost:8000/admin/users?per_page=20', $this->crawler()->filter('select option[selected]')->attr('value'));
+
+        $perPage = mt_rand(1, 98);
+
+        $this->visit('admin/users?per_page='.$perPage)
+            ->seeInElement('select option[selected]', $perPage)
+            ->assertCount($perPage + 1, $this->crawler()->filter('tr'));
+    }
+}

+ 81 - 0
tests/Feature/UserSettingTest.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace Tests\Feature;
+
+use Dcat\Admin\Models\Administrator;
+use Tests\TestCase;
+use Illuminate\Support\Facades\File;
+
+class UserSettingTest extends TestCase
+{
+    protected $login = true;
+
+    public function testVisitSettingPage()
+    {
+        $this->visit('admin/auth/setting')
+            ->see('User setting')
+            ->see('Username')
+            ->see('Name')
+            ->see('Avatar')
+            ->see('Password')
+            ->see('Password confirmation');
+
+        $this->seeElement('input[value=Administrator]')
+            ->seeInElement('.box-body', 'administrator');
+    }
+
+    public function testUpdateName()
+    {
+        $data = [
+            'name' => 'tester',
+        ];
+
+        $this->visit('admin/auth/setting')
+            ->submitForm('Submit', $data)
+            ->seePageIs('admin/auth/setting');
+
+        $this->seeInDatabase('admin_users', ['name' => $data['name']]);
+    }
+
+    public function testUpdatePasswordConfirmation()
+    {
+        $data = [
+            'password'              => '123456',
+            'password_confirmation' => '123',
+        ];
+
+        $this->visit('admin/auth/setting')
+            ->submitForm('Submit', $data)
+            ->seePageIs('admin/auth/setting')
+            ->see('The Password confirmation does not match.');
+    }
+
+    public function testUpdatePassword()
+    {
+        $data = [
+            'old_password'          => 'admin',
+            'password'              => '123456',
+            'password_confirmation' => '123456',
+        ];
+
+        $this->visit('admin/auth/setting')
+            ->submitForm('Submit', $data)
+            ->seePageIs('admin/auth/setting');
+
+        $this->assertTrue(app('hash')->check($data['password'], Administrator::first()->makeVisible('password')->password));
+
+        $this->visit('admin/auth/logout')
+            ->seePageIs('admin/auth/login')
+            ->dontSeeIsAuthenticated('admin');
+
+        $credentials = ['username' => 'admin', 'password' => '123456'];
+
+        $this->visit('admin/auth/login')
+            ->see('login')
+            ->submitForm('Login', $credentials)
+            ->see('dashboard')
+            ->seeCredentials($credentials, 'admin')
+            ->seeIsAuthenticated('admin')
+            ->seePageIs('admin');
+    }
+}

+ 89 - 0
tests/Feature/UsersTest.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Tests\Feature;
+
+use Dcat\Admin\Models\Administrator;
+use Tests\TestCase;
+
+class UsersTest extends TestCase
+{
+    protected $login = true;
+
+    public function testUsersIndexPage()
+    {
+        $this->visit('admin/auth/users')
+            ->see('Administrator');
+    }
+
+    public function testCreateUser()
+    {
+        $user = [
+            'username'              => 'Test',
+            'name'                  => 'Name',
+            'password'              => '123456',
+            'password_confirmation' => '123456',
+        ];
+
+        // create user
+        $this->visit('admin/auth/users/create')
+            ->see('Create')
+            ->submitForm('Submit', $user)
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.users_table'), ['username' => $user['username'], 'name' => $user['name']]);
+
+        // assign role to user
+        $this->visit('admin/auth/users/2/edit')
+            ->see('Edit')
+            ->submitForm('Submit', ['roles' => [1]])
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.role_users_table'), ['user_id' => 2, 'role_id' => 1]);
+
+        $this->visit('admin/auth/logout')
+            ->dontSeeIsAuthenticated('admin')
+            ->seePageIs('admin/auth/login')
+            ->submitForm('Login', ['username' => $user['username'], 'password' => $user['password']])
+            ->see('dashboard')
+            ->seeIsAuthenticated('admin')
+            ->seePageIs('admin');
+
+        $this->assertTrue($this->app['auth']->guard('admin')->getUser()->isAdministrator());
+
+        $this->see('<span>Users</span>')
+            ->see('<span>Roles</span>')
+            ->see('<span>Permission</span>')
+            ->see('<span>Operation log</span>')
+            ->see('<span>Menu</span>');
+    }
+
+    public function testUpdateUser()
+    {
+        $this->visit('admin/auth/users/'.$this->user->id.'/edit')
+            ->see('Edit')
+            ->submitForm('Submit', ['name' => 'test', 'roles' => [1]])
+            ->seePageIs('admin/auth/users')
+            ->seeInDatabase(config('admin.database.users_table'), ['name' => 'test']);
+    }
+
+    public function testResetPassword()
+    {
+        $password = 'odjwyufkglte';
+
+        $data = [
+            'password'              => $password,
+            'password_confirmation' => $password,
+            'roles'                 => [1],
+        ];
+
+        $this->visit('admin/auth/users/'.$this->user->id.'/edit')
+            ->see('Edit')
+            ->submitForm('Submit', $data)
+            ->seePageIs('admin/auth/users')
+            ->visit('admin/auth/logout')
+            ->dontSeeIsAuthenticated('admin')
+            ->seePageIs('admin/auth/login')
+            ->submitForm('Login', ['username' => $this->user->username, 'password' => $password])
+            ->see('dashboard')
+            ->seeIsAuthenticated('admin')
+            ->seePageIs('admin');
+    }
+}

+ 10 - 0
tests/Models/File.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace Tests\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class File extends Model
+{
+    protected $table = 'test_files';
+}

+ 10 - 0
tests/Models/Image.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace Tests\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Image extends Model
+{
+    protected $table = 'test_images';
+}

+ 22 - 0
tests/Models/MultipleImage.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace Tests\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class MultipleImage extends Model
+{
+    protected $table = 'test_multiple_images';
+
+    public function setPicturesAttribute($pictures)
+    {
+        if (is_array($pictures)) {
+            $this->attributes['pictures'] = json_encode($pictures);
+        }
+    }
+
+    public function getPicturesAttribute($pictures)
+    {
+        return json_decode($pictures, true) ?: [];
+    }
+}

+ 15 - 0
tests/Models/Profile.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Tests\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Profile extends Model
+{
+    protected $table = 'test_user_profiles';
+
+    public function user()
+    {
+        return $this->belongsTo(User::class, 'user_id');
+    }
+}

+ 15 - 0
tests/Models/Tag.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Tests\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Tag extends Model
+{
+    protected $table = 'test_tags';
+
+    public function users()
+    {
+        return $this->belongsToMany(User::class, 'test_user_tags', 'tag_id', 'user_id');
+    }
+}

+ 28 - 0
tests/Models/Tree.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Tests\Models;
+
+use Encore\Admin\Traits\AdminBuilder;
+use Encore\Admin\Traits\ModelTree;
+use Illuminate\Database\Eloquent\Model;
+
+class Tree extends Model
+{
+    use AdminBuilder, ModelTree;
+
+    /**
+     * Create a new Eloquent model instance.
+     *
+     * @param array $attributes
+     */
+    public function __construct(array $attributes = [])
+    {
+        $connection = config('admin.database.connection') ?: config('database.default');
+
+        $this->setConnection($connection);
+
+        $this->setTable(config('admin.database.menu_table'));
+
+        parent::__construct($attributes);
+    }
+}

+ 40 - 0
tests/Models/User.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Tests\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class User extends Model
+{
+    protected $table = 'test_users';
+
+    protected $appends = ['full_name', 'position'];
+
+    public function profile()
+    {
+        return $this->hasOne(Profile::class, 'user_id');
+    }
+
+    public function getFullNameAttribute()
+    {
+        if (!$this->profile) {
+            return;
+        }
+
+        return "{$this->profile['first_name']} {$this->profile['last_name']}";
+    }
+
+    public function getPositionAttribute()
+    {
+        if (!$this->profile) {
+            return;
+        }
+
+        return "{$this->profile->latitude} {$this->profile->longitude}";
+    }
+
+    public function tags()
+    {
+        return $this->belongsToMany(Tag::class, 'test_user_tags', 'user_id', 'tag_id');
+    }
+}

+ 11 - 0
tests/Repositories/User.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Tests\Repositories;
+
+use Dcat\Admin\Repositories\EloquentRepository;
+
+class User extends EloquentRepository
+
+{
+    protected $eloquentClass = \Tests\Models\User::class;
+}

+ 90 - 0
tests/TestCase.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace Tests;
+
+use Dcat\Admin\Models\Administrator;
+use Illuminate\Filesystem\Filesystem;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+use Laravel\BrowserKitTesting\TestCase as BaseTestCase;
+
+class TestCase extends BaseTestCase
+{
+    use CreatesApplication;
+
+    protected $baseUrl = 'http://localhost:8000';
+
+    /**
+     * @var Administrator
+     */
+    protected $user;
+
+    protected $login = false;
+
+    public function setUp(): void
+    {
+        parent::setUp();
+
+        $adminConfig = require __DIR__.'/config/admin.php';
+
+        $this->app['config']->set('database.default', 'mysql');
+        $this->app['config']->set('database.connections.mysql.host', env('MYSQL_HOST', 'localhost'));
+        $this->app['config']->set('database.connections.mysql.database', 'laravel_dcat_admin_test');
+        $this->app['config']->set('database.connections.mysql.username', 'root');
+        $this->app['config']->set('database.connections.mysql.password', '');
+        $this->app['config']->set('app.key', 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF');
+        $this->app['config']->set('filesystems', require __DIR__.'/config/filesystems.php');
+        $this->app['config']->set('admin', $adminConfig);
+
+        foreach (Arr::dot(Arr::get($adminConfig, 'auth'), 'auth.') as $key => $value) {
+            $this->app['config']->set($key, $value);
+        }
+
+        $this->artisan('vendor:publish', ['--provider' => 'Dcat\Admin\AdminServiceProvider']);
+
+        Schema::defaultStringLength(191);
+
+        $this->artisan('admin:install');
+
+        $this->migrateTestTables();
+
+        if (file_exists($routes = admin_path('routes.php'))) {
+            require $routes;
+        }
+
+        require __DIR__.'/routes.php';
+
+        require __DIR__.'/seeds/factory.php';
+
+        if ($this->login) {
+            $this->be($this->user = Administrator::first(), 'admin');
+        }
+    }
+
+    public function tearDown(): void
+    {
+        (new \CreateAdminTables())->down();
+
+        (new \CreateTestTables())->down();
+
+        DB::select("delete from `migrations` where `migration` = '2016_01_04_173148_create_admin_tables'");
+        DB::select("delete from `migrations` where `migration` = '2016_11_22_093148_create_test_tables'");
+
+        parent::tearDown();
+    }
+
+    /**
+     * run package database migrations.
+     *
+     * @return void
+     */
+    public function migrateTestTables()
+    {
+        $fileSystem = new Filesystem();
+
+        $fileSystem->requireOnce(__DIR__.'/migrations/2016_11_22_093148_create_test_tables.php');
+
+        (new \CreateTestTables())->up();
+    }
+}

BIN
tests/assets/test.jpg


+ 376 - 0
tests/config/admin.php

@@ -0,0 +1,376 @@
+<?php
+
+use Dcat\Admin\Controllers\AuthController;
+use Dcat\Admin\Grid\Displayers\DropdownActions;
+use Dcat\Admin\Models\Administrator;
+use Dcat\Admin\Models\Menu;
+use Dcat\Admin\Models\Permission;
+use Dcat\Admin\Models\Role;
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin name
+    |--------------------------------------------------------------------------
+    |
+    | This value is the name of dcat-admin, This setting is displayed on the
+    | login page.
+    |
+    */
+    'name' => 'Dcat Admin',
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin logo
+    |--------------------------------------------------------------------------
+    |
+    | The logo of all admin pages. You can also set it as an image by using a
+    | `img` tag, eg '<img src="http://logo-url" alt="Admin logo">'.
+    |
+    */
+    'logo' => '<span>Dcat</span> Admin',
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin mini logo
+    |--------------------------------------------------------------------------
+    |
+    | The logo of all admin pages when the sidebar menu is collapsed. You can
+    | also set it as an image by using a `img` tag, eg
+    | '<img src="http://logo-url" alt="Admin logo">'.
+    |
+    */
+    'logo-mini' => '<b>Da</b>',
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin route settings
+    |--------------------------------------------------------------------------
+    |
+    | The routing configuration of the admin page, including the path prefix,
+    | the controller namespace, and the default middleware. If you want to
+    | access through the root path, just set the prefix to empty string.
+    |
+    */
+    'route' => [
+
+        'prefix' => env('ADMIN_ROUTE_PREFIX', 'admin'),
+
+        'namespace' => 'App\\Admin\\Controllers',
+
+        'middleware' => ['web', 'admin'],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin install directory
+    |--------------------------------------------------------------------------
+    |
+    | The installation directory of the controller and routing configuration
+    | files of the administration page. The default is `app/Admin`, which must
+    | be set before running `artisan admin::install` to take effect.
+    |
+    */
+    'directory' => app_path('Admin'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin html title
+    |--------------------------------------------------------------------------
+    |
+    | Html title for all pages.
+    |
+    */
+    'title' => 'Admin',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Assets hostname
+    |--------------------------------------------------------------------------
+    |
+   */
+    'assets_server' => env('ADMIN_ASSETS_SERVER'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Cdn setting
+    |--------------------------------------------------------------------------
+    |
+   */
+    'cdn' => false,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Access via `https`
+    |--------------------------------------------------------------------------
+    |
+    | If your page is going to be accessed via https, set it to `true`.
+    |
+    */
+    'https' => env('ADMIN_HTTPS', false),
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin auth setting
+    |--------------------------------------------------------------------------
+    |
+    | Authentication settings for all admin pages. Include an authentication
+    | guard and a user provider setting of authentication driver.
+    |
+    | You can specify a controller for `login` `logout` and other auth routes.
+    |
+    */
+    'auth' => [
+
+        'controller' => Dcat\Admin\Controllers\AuthController::class,
+
+        'guard' => 'admin',
+
+        'guards' => [
+            'admin' => [
+                'driver'   => 'session',
+                'provider' => 'admin',
+            ],
+        ],
+
+        'providers' => [
+            'admin' => [
+                'driver' => 'eloquent',
+                'model'  => Dcat\Admin\Models\Administrator::class,
+            ],
+        ],
+
+        // Add "remember me" to login form
+        'remember' => true,
+
+        // All method to path like: auth/users/*/edit
+        // or specific method to path like: get:auth/users.
+        'except' => [
+            'auth/login',
+            'auth/logout',
+        ],
+
+    ],
+
+    'grid' => [
+
+        /*
+        |--------------------------------------------------------------------------
+        | The global Grid action display class.
+        |--------------------------------------------------------------------------
+        */
+        'grid_action_class' => Dcat\Admin\Grid\Displayers\DropdownActions::class,
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin permission setting
+    |--------------------------------------------------------------------------
+    |
+    | Permission settings for all admin pages.
+    |
+    */
+    'permission' => [
+        // Whether enable permission.
+        'enable' => true,
+
+        // All method to path like: auth/users/*/edit
+        // or specific method to path like: get:auth/users.
+        'except' => [
+            '/',
+            'auth/login',
+            'auth/logout',
+            'auth/setting',
+        ],
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin menu setting
+    |--------------------------------------------------------------------------
+    |
+    */
+    'menu' => [
+        'cache' => [
+            // enable cache or not
+            'enable' => true,
+            'store'  => 'file',
+        ],
+
+        // Whether enable menu bind to a permission.
+        'bind_permission' => true,
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin upload setting
+    |--------------------------------------------------------------------------
+    |
+    | File system configuration for form upload files and images, including
+    | disk and upload path.
+    |
+    */
+    'upload' => [
+
+        // Disk in `config/filesystem.php`.
+        'disk' => 'admin',
+
+        // Image and file upload path under the disk above.
+        'directory' => [
+            'image' => 'images',
+            'file'  => 'files',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | dcat-admin database settings
+    |--------------------------------------------------------------------------
+    |
+    | Here are database settings for dcat-admin builtin model & tables.
+    |
+    */
+    'database' => [
+
+        // Database connection for following tables.
+        'connection' => '',
+
+        // User tables and model.
+        'users_table' => 'admin_users',
+        'users_model' => Dcat\Admin\Models\Administrator::class,
+
+        // Role table and model.
+        'roles_table' => 'admin_roles',
+        'roles_model' => Dcat\Admin\Models\Role::class,
+
+        // Permission table and model.
+        'permissions_table' => 'admin_permissions',
+        'permissions_model' => Dcat\Admin\Models\Permission::class,
+
+        // Menu table and model.
+        'menu_table' => 'admin_menu',
+        'menu_model' => Dcat\Admin\Models\Menu::class,
+
+        // Pivot table for table above.
+        'operation_log_table'    => 'admin_operation_log',
+        'user_permissions_table' => 'admin_user_permissions',
+        'role_users_table'       => 'admin_role_users',
+        'role_permissions_table' => 'admin_role_permissions',
+        'role_menu_table'        => 'admin_role_menu',
+        'permission_menu_table'  => 'admin_permission_menu',
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | User operation log setting
+    |--------------------------------------------------------------------------
+    |
+    | By setting this option to open or close operation log in dcat-admin.
+    |
+    */
+    'operation_log' => [
+
+        'enable' => true,
+
+        // Only logging allowed methods in the list
+        'allowed_methods' => ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'],
+
+        // Routes that will not log to database.
+        // All method to path like: auth/logs/*/edit
+        // or specific method to path like: get:auth/logs.
+        'except' => [
+            'auth/logs*',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Admin map field provider
+    |--------------------------------------------------------------------------
+    |
+    | Supported: "tencent", "google", "yandex".
+    |
+    */
+    'map_provider' => 'google',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Skin
+    |--------------------------------------------------------------------------
+    |
+    | This value is the skin of admin pages.
+    | @see https://adminlte.io/docs/2.4/layout
+    |
+    | Supported:
+    |    "skin-blue-light", "skin-black", "skin-black-light".
+    |
+    */
+    'skin' => 'skin-black',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application layout
+    |--------------------------------------------------------------------------
+    |
+    | This value is the layout of admin pages.
+    | @see https://adminlte.io/docs/2.4/layout
+    |
+    | Supported: "fixed", "layout-boxed", "layout-top-nav", "sidebar-collapse",
+    | "sidebar-mini".
+    |
+    */
+    'layout' => ['sidebar-mini', 'sidebar-collapse', 'fixed'],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Login page background image
+    |--------------------------------------------------------------------------
+    |
+    | This value is used to set the background image of login page.
+    |
+    */
+    'login_background_image' => '',
+
+    /*
+    |--------------------------------------------------------------------------
+    | The exception handler class
+    |--------------------------------------------------------------------------
+    |
+   */
+    'exception_handler' => \Dcat\Admin\Exception\Handler::class,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Enable default breadcrumb
+    |--------------------------------------------------------------------------
+    |
+    | Whether enable default breadcrumb for every page content.
+    */
+    'enable_default_breadcrumb' => true,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Extension Directory
+    |--------------------------------------------------------------------------
+    |
+    | When you use command `php artisan admin:extend` to generate extensions,
+    | the extension files will be generated in this directory.
+    */
+    'extension_dir' => app_path('Admin/Extensions'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Settings for extensions.
+    |--------------------------------------------------------------------------
+    |
+    | You can find all available extensions here
+    | https://github.com/dcat-admin-extensions.
+    |
+    */
+    'extensions' => [
+
+    ],
+];

+ 95 - 0
tests/config/filesystems.php

@@ -0,0 +1,95 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Filesystem Disk
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify the default filesystem disk that should be used
+    | by the framework. A "local" driver, as well as a variety of cloud
+    | based drivers are available for your choosing. Just store away!
+    |
+    | Supported: "local", "ftp", "s3", "rackspace"
+    |
+    */
+
+    'default' => 'public',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Cloud Filesystem Disk
+    |--------------------------------------------------------------------------
+    |
+    | Many applications store files both locally and in the cloud. For this
+    | reason, you may specify a default "cloud" driver here. This driver
+    | will be bound as the Cloud disk implementation in the container.
+    |
+    */
+
+    'cloud' => 's3',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Filesystem Disks
+    |--------------------------------------------------------------------------
+    |
+    | Here you may configure as many filesystem "disks" as you wish, and you
+    | may even configure multiple disks of the same driver. Defaults have
+    | been setup for each driver as an example of the required options.
+    |
+    */
+
+    'disks' => [
+
+        'local' => [
+            'driver' => 'local',
+            'root'   => storage_path('app'),
+        ],
+
+        'public' => [
+            'driver'     => 'local',
+            'root'       => storage_path('app/public'),
+            'visibility' => 'public',
+        ],
+
+        's3' => [
+            'driver' => 's3',
+            'key'    => 'your-key',
+            'secret' => 'your-secret',
+            'region' => 'your-region',
+            'bucket' => 'your-bucket',
+        ],
+
+        'admin' => [
+            'driver'     => 'local',
+            'root'       => public_path('uploads'),
+            'visibility' => 'public',
+            'url'        => 'http://localhost:8000/uploads/',
+        ],
+
+        'qiniu' => [
+            'driver'  => 'qiniu',
+            'domains' => [
+                'default' => 'of8kfibjo.bkt.clouddn.com', //你的七牛域名
+                'https'   => 'dn-yourdomain.qbox.me',         //你的HTTPS域名
+                'custom'  => 'static.abc.com',                //你的自定义域名
+            ],
+            'access_key' => 'tIyz5h5IDT1-PQS22iRrI4dCBEktWj76O-ls856K',  //AccessKey
+            'secret_key' => 'TCU2GuSlbzxKgnixYO_-pdo4odbXttm1RNNvEwSD',  //SecretKey
+            'bucket'     => 'laravel',  //Bucket名字
+            'notify_url' => '',  //持久化处理回调地址
+        ],
+
+        'aliyun' => [
+            'driver'     => 'oss',
+            'access_id'  => 'LTAIsOQNIDQN78Jr',
+            'access_key' => 'ChsYewaCxm1mi7AIBPRniuncEbFHNO',
+            'bucket'     => 'laravel-admin',
+            'endpoint'   => 'oss-cn-shanghai.aliyuncs.com',
+        ],
+
+    ],
+
+];

+ 98 - 0
tests/migrations/2016_11_22_093148_create_test_tables.php

@@ -0,0 +1,98 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+
+class CreateTestTables extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('test_images', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('image1');
+            $table->string('image2');
+            $table->string('image3');
+            $table->string('image4');
+            $table->string('image5');
+            $table->string('image6');
+            $table->timestamps();
+        });
+
+        Schema::create('test_multiple_images', function (Blueprint $table) {
+            $table->increments('id');
+            $table->text('pictures');
+            $table->timestamps();
+        });
+
+        Schema::create('test_files', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('file1');
+            $table->string('file2');
+            $table->string('file3');
+            $table->string('file4');
+            $table->string('file5');
+            $table->string('file6');
+            $table->timestamps();
+        });
+
+        Schema::create('test_users', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('username');
+            $table->string('email');
+            $table->string('mobile')->nullable();
+            $table->string('avatar')->nullable();
+            $table->string('password');
+            $table->timestamps();
+        });
+
+        Schema::create('test_user_profiles', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('user_id');
+            $table->string('first_name')->nullable();
+            $table->string('last_name')->nullable();
+            $table->string('postcode')->nullable();
+            $table->string('address')->nullable();
+            $table->string('latitude')->nullable();
+            $table->string('longitude')->nullable();
+            $table->string('color')->nullable();
+            $table->timestamp('start_at')->nullable();
+            $table->timestamp('end_at')->nullable();
+
+            $table->timestamps();
+        });
+
+        Schema::create('test_tags', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('name');
+            $table->timestamps();
+        });
+
+        Schema::create('test_user_tags', function (Blueprint $table) {
+            $table->integer('user_id');
+            $table->integer('tag_id');
+            $table->index(['user_id', 'tag_id']);
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('test_images');
+        Schema::dropIfExists('test_multiple_images');
+        Schema::dropIfExists('test_files');
+        Schema::dropIfExists('test_users');
+        Schema::dropIfExists('test_user_profiles');
+        Schema::dropIfExists('test_tags');
+        Schema::dropIfExists('test_user_tags');
+    }
+}

+ 12 - 0
tests/routes.php

@@ -0,0 +1,12 @@
+<?php
+
+Route::group([
+    'prefix'     => config('admin.route.prefix'),
+    'namespace'  => 'Tests\Controllers',
+    'middleware' => ['web', 'admin'],
+], function ($router) {
+    $router->resource('images', ImageController::class);
+    $router->resource('multiple-images', MultipleImageController::class);
+    $router->resource('files', FileController::class);
+    $router->resource('users', UserController::class);
+});

+ 18 - 0
tests/seeds/UserTableSeeder.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Tests\Seeds;
+
+use Illuminate\Database\Seeder;
+
+class UserTableSeeder extends Seeder
+{
+    public function run()
+    {
+        factory(\Tests\Models\User::class, 50)
+            ->create()
+            ->each(function ($u) {
+                $u->profile()->save(factory(\Tests\Models\Profile::class)->make());
+                $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make());
+            });
+    }
+}

+ 36 - 0
tests/seeds/factory.php

@@ -0,0 +1,36 @@
+<?php
+
+use Faker\Generator as Faker;
+use Illuminate\Database\Eloquent\Factory;
+
+$factory = app(Factory::class);
+
+$factory->define(Tests\Models\User::class, function (Faker $faker) {
+    return [
+        'username' => $faker->userName,
+        'email'    => $faker->email,
+        'mobile'   => $faker->phoneNumber,
+        'avatar'   => $faker->imageUrl(),
+        'password' => bcrypt('123456'),
+    ];
+});
+
+$factory->define(Tests\Models\Profile::class, function (Faker $faker) {
+    return [
+        'first_name' => $faker->firstName,
+        'last_name'  => $faker->lastName,
+        'postcode'   => $faker->postcode,
+        'address'    => $faker->address,
+        'latitude'   => $faker->latitude,
+        'longitude'  => $faker->longitude,
+        'color'      => $faker->hexColor,
+        'start_at'   => $faker->dateTime,
+        'end_at'     => $faker->dateTime,
+    ];
+});
+
+$factory->define(Tests\Models\Tag::class, function (Faker $faker) {
+    return [
+        'name' => $faker->word,
+    ];
+});