diff --git a/.gitattributes b/.gitattributes
index 8fe1ba9..fe80458 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -9,6 +9,9 @@
/.github export-ignore
/build export-ignore
/tests export-ignore
+/.editorconfig export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
/CHANGELOG.md export-ignore
/CODE_OF_CONDUCT.md export-ignore
/CONTRIBUTING.md export-ignore
@@ -16,5 +19,6 @@
/phpstan-baseline.neon export-ignore
/phpunit.xml.dist export-ignore
/pint.json export-ignore
+/README.md export-ignore
/rector.php export-ignore
/Roadmap.md export-ignore
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5dace46..7fd1732 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -41,7 +41,7 @@ jobs:
echo "cache-key=${{ env.cache_key }}" >> "$GITHUB_OUTPUT"
echo "extensions=${{ env.extensions }}" >> "$GITHUB_OUTPUT"
echo "tools=${{ env.tools }}" >> "$GITHUB_OUTPUT"
- echo "php_versions=['8.1', '8.2']" >> "$GITHUB_OUTPUT"
+ echo "php_versions=['8.1', '8.2', '8.3']" >> "$GITHUB_OUTPUT"
echo "php_version=['8.1']" >> "$GITHUB_OUTPUT"
code-style:
@@ -69,6 +69,7 @@ jobs:
cache_key: ${{ needs.env-setup.outputs.cache-key }}
extensions: ${{ needs.env-setup.outputs.extensions }}
min_coverage: 100
+ type_coverage: 100
rector:
needs: [ env-setup ]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0152b8..8bb3d5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# Release Notes for 2.x
+
+## [2.0.0 (2023-12-17)](https://github.com/Jampire/moonshine-impersonate/compare/v1.4.1...v2.0.0)
+
+### Adds
+
+- MoonShine v2 support, Belarus Localization ([#25](https://github.com/Jampire/moonshine-impersonate/pull/25))
+
# Release Notes for 1.x
## [1.4.1 (2023-12-15)](https://github.com/Jampire/moonshine-impersonate/compare/v1.4.0...v1.4.1)
diff --git a/README.md b/README.md
index 8537db6..fb704f2 100644
--- a/README.md
+++ b/README.md
@@ -19,10 +19,11 @@ saves lots of time because you can see exactly what they see.
## Compatibility
-| MoonShine | MoonShine Impersonate |
-|:------------------------:|:-----------------------:|
-| \>= v1.52 and <= v1.57.4 | <= v1.2.0 |
-| >= v1.58.0 | >= v1.3.0 |
+| MoonShine | MoonShine Impersonate | Currently supported |
+|:------------------------:|:---------------------:|:-------------------:|
+| \>= v1.52 and <= v1.57.4 | <= v1.2.0 | no |
+| >= v1.58.0 | >= v1.3.0 | no |
+| >= v2.0 | >= v2.0 | yes |
## Installation
@@ -67,8 +68,8 @@ Please review and abide by the [Code of Conduct][5].
- [Laravel Orchid impersonation mode][10]
- [Laravel standalone package][11]
-[1]: https://moonshine.cutcode.dev/
-[2]: https://moonshine.cutcode.dev/section/installation
+[1]: https://moonshine-laravel.com/
+[2]: https://moonshine-laravel.com/docs/section/installation
[3]: https://dzianiskotau.com/moonshine-impersonate/
[4]: CONTRIBUTING.md
[5]: CODE_OF_CONDUCT.md
diff --git a/Roadmap.md b/Roadmap.md
index f78f2af..c1de998 100644
--- a/Roadmap.md
+++ b/Roadmap.md
@@ -1 +1,2 @@
- implement `database` provider
+- implement `Impersonation` resource
diff --git a/composer.json b/composer.json
index e8f93b6..4a160bf 100644
--- a/composer.json
+++ b/composer.json
@@ -25,17 +25,19 @@
"source": "https://github.com/Jampire/moonshine-impersonate"
},
"require": {
- "php": "^8.1|^8.2"
+ "php": "^8.1|^8.2|^8.3"
},
"require-dev": {
"driftingly/rector-laravel": "^0.20.0",
"laravel/pint": "^1.10",
- "moonshine/moonshine": "^1.56",
+ "moonshine/changelog": "^1.0",
+ "moonshine/moonshine": "^2.0",
"nunomaduro/collision": "^7.5",
"nunomaduro/larastan": "^2.0",
"orchestra/testbench": "^8.5",
"pestphp/pest": "^2.6",
"pestphp/pest-plugin-laravel": "^2.0",
+ "pestphp/pest-plugin-type-coverage": "^2.5",
"rector/rector": "^0.16.0"
},
"autoload": {
@@ -52,7 +54,7 @@
}
},
"conflict": {
- "moonshine/moonshine": "<1.58.0 || >= 2"
+ "moonshine/moonshine": "<2"
},
"scripts": {
"post-autoload-dump": [
@@ -65,10 +67,13 @@
"analyse": "./vendor/bin/phpstan analyse --ansi --memory-limit=-1",
"tests": "./vendor/bin/pest --no-coverage --parallel",
"tests-coverage": "XDEBUG_MODE=coverage ./vendor/bin/pest --coverage --min=100 --parallel",
+ "tests-type": "./vendor/bin/pest --type-coverage --min=100 --parallel",
"all": [
"@composer validate --strict --ansi",
"@style",
+ "@rector",
"@analyse",
+ "@tests-type",
"@tests-coverage"
]
},
diff --git a/config/impersonate.php b/config/impersonate.php
index b0b440b..f938225 100644
--- a/config/impersonate.php
+++ b/config/impersonate.php
@@ -12,7 +12,7 @@
'routes' => [
// impersonate routes prefix
- 'prefix' => env('MS_IMPERSONATE_ROUTE_PREFIX', 'impersonate'),
+ 'prefix' => env('MS_IMPERSONATE_ROUTE_PREFIX', config('moonshine.route.prefix').'/impersonate'),
// what middleware is used for routes to enter/stop impersonation
'middleware' => ['web'],
@@ -23,10 +23,14 @@
'icon' => env('MS_IMPERSONATE_ENTER_BUTTON_ICON', 'heroicons.outline.eye')
],
'stop' => [
- // if true the button will be set to the header of the page
- 'enabled' => env('MS_IMPERSONATE_STOP_BUTTON_ENABLED', true),
'icon' => env('MS_IMPERSONATE_STOP_BUTTON_ICON', 'heroicons.outline.eye-slash'),
- 'class' => env('MS_IMPERSONATE_STOP_BUTTON_CLASS', 'btn-pink'),
+ 'class' => env('MS_IMPERSONATE_STOP_BUTTON_CLASS', 'btn-secondary'),
],
],
+
+ // query string key name for resource item
+ 'resource_item_key' => env('MS_IMPERSONATE_RESOURCE_ITEM_KEY', 'resourceItem'),
+
+ // show 'toast' notifications on different actions
+ 'show_notification' => env('MS_IMPERSONATE_SHOW_NOTIFICATION', true),
];
diff --git a/lang/be/ui.php b/lang/be/ui.php
new file mode 100644
index 0000000..a413b19
--- /dev/null
+++ b/lang/be/ui.php
@@ -0,0 +1,14 @@
+ [
+ 'enter' => [
+ 'label' => 'Падмяніць карыстальніка',
+ 'message' => 'Вы паспяхова пераключыліся на іншага карыстальніка.',
+ ],
+ 'stop' => [
+ 'label' => 'Спыніць падмену карыстальніка',
+ 'message' => 'Падмена карыстальніка была паспяхова спынена!',
+ ],
+ ],
+];
diff --git a/lang/be/validation.php b/lang/be/validation.php
new file mode 100644
index 0000000..2df6d90
--- /dev/null
+++ b/lang/be/validation.php
@@ -0,0 +1,13 @@
+ [
+ 'is_impersonating' => 'Вы ўжо ў рэжыме падмены карыстальніка. Спачатку спыніце бягучы рэжым падмены!',
+ 'cannot_impersonate' => 'У вас няма правоў для падмены іншых карыстальнікаў.',
+ 'cannot_be_impersonated' => 'Гэты карыстастальнік не можа быць падменены.',
+ 'id' => 'ID Карыстальніка',
+ ],
+ 'stop' => [
+ 'is_not_impersonating' => 'Ніякі карыстастальнік не падменены. Няма чаго рабіць.',
+ ],
+];
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 91268aa..16ff8a8 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -12,6 +12,11 @@
processIsolation="false"
stopOnFailure="false"
>
+
./tests/Unit
diff --git a/resources/views/components/impersonate-stop.blade.php b/resources/views/components/impersonate-stop.blade.php
index 28a3eaa..ace0d12 100644
--- a/resources/views/components/impersonate-stop.blade.php
+++ b/resources/views/components/impersonate-stop.blade.php
@@ -1,3 +1,5 @@
-
- {{ $label }}
-
+@if($canStop)
+
+ {{ $label }}
+
+@endif
diff --git a/resources/views/impersonate/buttons/stop.blade.php b/resources/views/impersonate/buttons/stop.blade.php
deleted file mode 100644
index 889444d..0000000
--- a/resources/views/impersonate/buttons/stop.blade.php
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/routes/web.php b/routes/web.php
index 29f1d3d..5b1f0ff 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -5,12 +5,17 @@
use Illuminate\Support\Facades\Route;
use Jampire\MoonshineImpersonate\Http\Controllers\ImpersonateController;
use Jampire\MoonshineImpersonate\Support\Settings;
+use MoonShine\Http\Middleware\ChangeLocale;
+
+$middlewares = config_impersonate('routes.middleware');
+$middlewares[] = ChangeLocale::class;
Route::controller(ImpersonateController::class)
->name(Settings::ALIAS.'.')
->prefix(config_impersonate('routes.prefix'))
- ->middleware(config_impersonate('routes.middleware'))
+ ->middleware($middlewares)
->group(function (): void {
- Route::post('/enter', 'enter')->name('enter');
+ Route::get('/enter', 'enter')->name('enter');
+ Route::post('/enter', 'enter')->name('enter-confirm');
Route::get('/stop', 'stop')->name('stop');
});
diff --git a/src/Actions/EnterAction.php b/src/Actions/EnterAction.php
index eba9eba..629adf3 100644
--- a/src/Actions/EnterAction.php
+++ b/src/Actions/EnterAction.php
@@ -23,7 +23,7 @@ public function __construct(
/**
* @param int $id ID of the impersonated user
*/
- public function execute(int $id, bool $shouldValidate = false): bool
+ public function execute(int $id, bool $shouldValidate = true): bool
{
$user = $this->manager->findUserById($id);
diff --git a/src/Actions/StopAction.php b/src/Actions/StopAction.php
index 4982d27..0e2bba4 100644
--- a/src/Actions/StopAction.php
+++ b/src/Actions/StopAction.php
@@ -26,6 +26,7 @@ public function __construct(
public function execute(): bool
{
$user = $this->manager->getUserFromSession();
+
if (!$user instanceof Authenticatable) {
return false;
}
diff --git a/src/Enums/State.php b/src/Enums/State.php
index 2770543..dd8dad5 100644
--- a/src/Enums/State.php
+++ b/src/Enums/State.php
@@ -1,5 +1,7 @@
execute($request->safe()->id)) {
- // TODO: flash message
+ $result = $action->execute(id: $request->safe()->id, shouldValidate: false);
+ if (!$result) {
// @codeCoverageIgnoreStart
+ toast_if(
+ condition: config_impersonate('show_notification'),
+ message: trans_impersonate('validation.enter.cannot_be_impersonated'),
+ type: 'error'
+ );
+
return redirect()->back();
// @codeCoverageIgnoreEnd
}
+ toast_if(
+ condition: config_impersonate('show_notification'),
+ message: trans_impersonate('ui.buttons.enter.message'),
+ type: 'success'
+ );
+
return redirect(config_impersonate('redirect_to'));
}
public function stop(StopFormRequest $request, StopAction $action): Redirector|RedirectResponse
{
- if (!$action->execute()) {
- // TODO: flash message
+ $result = $action->execute();
+ if (!$result) {
// @codeCoverageIgnoreStart
+ toast_if(
+ condition: config_impersonate('show_notification'),
+ message: trans_impersonate('validation.stop.is_not_impersonating'),
+ type: 'error'
+ );
+
return redirect()->back();
// @codeCoverageIgnoreEnd
}
+ toast_if(
+ condition: config_impersonate('show_notification'),
+ message: trans_impersonate('ui.buttons.stop.message'),
+ type: 'success'
+ );
+
return redirect(config_impersonate('redirect_to'));
}
}
diff --git a/src/Http/Middleware/ImpersonateMiddleware.php b/src/Http/Middleware/ImpersonateMiddleware.php
index 20e0c33..313503c 100644
--- a/src/Http/Middleware/ImpersonateMiddleware.php
+++ b/src/Http/Middleware/ImpersonateMiddleware.php
@@ -1,5 +1,7 @@
trans_impersonate('validation.enter.id'),
];
}
+
+ protected function prepareForValidation(): void
+ {
+ $key = config_impersonate('resource_item_key');
+
+ if ($this->has($key)) {
+ $this->merge([
+ 'id' => (int)$this->get($key),
+ ]);
+ }
+ }
}
diff --git a/src/ImpersonateServiceProvider.php b/src/ImpersonateServiceProvider.php
index 5f36980..44cbe29 100644
--- a/src/ImpersonateServiceProvider.php
+++ b/src/ImpersonateServiceProvider.php
@@ -14,7 +14,6 @@
use Jampire\MoonshineImpersonate\Providers\EventServiceProvider;
use Jampire\MoonshineImpersonate\Services\ImpersonateManager;
use Jampire\MoonshineImpersonate\Support\Settings;
-use Jampire\MoonshineImpersonate\UI\View\Components\StopImpersonation;
/**
* Class ImpersonateServiceProvider
@@ -76,7 +75,7 @@ private function registerAuthDriver(): void
{
$auth = app('auth');
- $auth->extend('session', function (Application $app, $name, array $config) use ($auth): SessionGuard {
+ $auth->extend('session', function (Application $app, string $name, array $config) use ($auth): SessionGuard {
$provider = $auth->createUserProvider($config['provider']);
$guard = new SessionGuard($name, $provider, $app['session.store']);
@@ -99,18 +98,8 @@ private function registerAuthDriver(): void
private function registerViews(): void
{
- $this->loadViewComponentsAs(Settings::ALIAS, [
- 'stop' => StopImpersonation::class,
- ]);
-
$this->loadViewsFrom(__DIR__.'/../resources/views', Settings::ALIAS);
- if (config_impersonate('buttons.stop.enabled') === true) {
- config([
- 'moonshine.header' => Settings::ALIAS . '::impersonate.buttons.stop',
- ]);
- }
-
if ($this->app->runningUnitTests()) {
$this->loadViewsFrom(__DIR__.'/../tests/Stubs/resources/views', 'moonshine');
}
@@ -145,28 +134,5 @@ private function publishImpersonateResources(): void
'lang',
]
);
-
- // Views
- $this->publishes(
- [
- __DIR__.'/../resources/views/impersonate' => resource_path('views/vendor/impersonate')
- ],
- [
- Settings::ALIAS,
- 'views',
- ]
- );
-
- // Views Components
- $this->publishes(
- [
- __DIR__.'/../src/UI/View/Components' => app_path('View/Components'),
- __DIR__.'/../resources/views/components' => resource_path('views/components'),
- ],
- [
- Settings::ALIAS,
- 'view-components',
- ]
- );
}
}
diff --git a/src/Providers/EventServiceProvider.php b/src/Providers/EventServiceProvider.php
index f2481b8..5ef443b 100644
--- a/src/Providers/EventServiceProvider.php
+++ b/src/Providers/EventServiceProvider.php
@@ -1,5 +1,7 @@
$user->getAuthIdentifier(),
+ Settings::key() => $user->getAuthIdentifier(),
Settings::impersonatorSessionKey() => $this->moonshineUser->getAuthIdentifier(),
Settings::impersonatorSessionGuardKey() => Settings::moonShineGuard(),
]);
@@ -98,7 +98,7 @@ public function saveAuthInSession(Authenticatable $user): void
public function clearAuthFromSession(): void
{
session()->forget([
- config_impersonate('key'),
+ Settings::key(),
Settings::impersonatorSessionKey(),
Settings::impersonatorSessionGuardKey(),
]);
diff --git a/src/Support/helpers.php b/src/Support/helpers.php
index 54983c4..c8830d5 100644
--- a/src/Support/helpers.php
+++ b/src/Support/helpers.php
@@ -3,6 +3,7 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\View\View;
use Jampire\MoonshineImpersonate\Support\Settings;
+use MoonShine\MoonShineUI;
if (!function_exists('route_impersonate')) {
/**
@@ -52,3 +53,17 @@ function view_impersonate(string $key, array|Arrayable $data = [], array $mergeD
return view(Settings::ALIAS.'::'.$key, $data, $mergeData);
}
}
+
+if (!function_exists('toast_if')) {
+ /**
+ * @author Dzianis Kotau
+ */
+ function toast_if(bool $condition, string $message, string $type = 'info'): void
+ {
+ if (!$condition) {
+ return;
+ }
+
+ MoonShineUI::toast(message: $message, type: $type);
+ }
+}
diff --git a/src/UI/ActionButtons/EnterImpersonationActionButton.php b/src/UI/ActionButtons/EnterImpersonationActionButton.php
new file mode 100644
index 0000000..70da780
--- /dev/null
+++ b/src/UI/ActionButtons/EnterImpersonationActionButton.php
@@ -0,0 +1,31 @@
+
+ */
+final class EnterImpersonationActionButton
+{
+ public static function resolve(): ActionButton
+ {
+ return ActionButton::make(
+ label: trans_impersonate('ui.buttons.enter.label'),
+ url: static fn (mixed $data): string => route_impersonate('enter', [
+ config_impersonate('resource_item_key') => $data->getKey(),
+ ]),
+ )
+ ->canSee(
+ callback: fn (Authenticatable $item): bool => app(EnterAction::class)->manager->canEnter($item),
+ )
+ ->icon(config_impersonate('buttons.enter.icon'));
+ }
+}
diff --git a/src/UI/ActionButtons/StopImpersonationActionButton.php b/src/UI/ActionButtons/StopImpersonationActionButton.php
new file mode 100644
index 0000000..5423433
--- /dev/null
+++ b/src/UI/ActionButtons/StopImpersonationActionButton.php
@@ -0,0 +1,28 @@
+
+ */
+final class StopImpersonationActionButton
+{
+ public static function resolve(): ActionButton
+ {
+ return ActionButton::make(
+ label: trans_impersonate('ui.buttons.stop.label'),
+ url: static fn (mixed $data): string => route_impersonate('stop'),
+ )
+ ->canSee(
+ callback: fn (): bool => app(ImpersonateManager::class)->canStop(),
+ )
+ ->icon(config_impersonate('buttons.stop.icon'));
+ }
+}
diff --git a/src/UI/Components/StopImpersonation.php b/src/UI/Components/StopImpersonation.php
new file mode 100644
index 0000000..5f6da49
--- /dev/null
+++ b/src/UI/Components/StopImpersonation.php
@@ -0,0 +1,46 @@
+
+ * @method static static make(string $route = null, string $label = null, string $icon = null, string $class = null)
+ */
+final class StopImpersonation extends MoonShineComponent
+{
+ public function __construct(
+ private readonly ?string $route = null,
+ private readonly ?string $label = null,
+ private readonly ?string $icon = null,
+ private readonly ?string $class = null,
+ ) {
+ //
+ }
+
+ public function getView(): string
+ {
+ return $this->customView ?? Settings::ALIAS.'::components.impersonate-stop';
+ }
+
+ /**
+ * @return array{canStop: bool, route: string, label: string, icon: string, class: string}
+ */
+ protected function viewData(): array
+ {
+ return [
+ 'canStop' => app(ImpersonateManager::class)->canStop(),
+ 'route' => $this->route ?? route_impersonate('stop'),
+ 'label' => $this->label ?? trans_impersonate('ui.buttons.stop.label'),
+ 'icon' => $this->icon ?? config_impersonate('buttons.stop.icon'),
+ 'class' => $this->class ?? config_impersonate('buttons.stop.class'),
+ ];
+ }
+}
diff --git a/src/UI/ItemActions/EnterImpersonationItemAction.php b/src/UI/ItemActions/EnterImpersonationItemAction.php
deleted file mode 100644
index b97e894..0000000
--- a/src/UI/ItemActions/EnterImpersonationItemAction.php
+++ /dev/null
@@ -1,35 +0,0 @@
-
- */
-final class EnterImpersonationItemAction implements ItemActionContract
-{
- use Makeable;
-
- public function resolve(): ItemAction
- {
- $action = app(EnterAction::class);
-
- return ItemAction::make(
- trans_impersonate('ui.buttons.enter.label'),
- fn (Authenticatable $item) => $action->execute($item->getAuthIdentifier(), true),
- trans_impersonate('ui.buttons.enter.message') // TODO: set message from $action->execute
- )
- ->canSee(fn (Authenticatable $item) => $action->manager->canEnter($item))
- ->icon(config_impersonate('buttons.enter.icon'))
- ;
- }
-}
diff --git a/src/UI/View/Components/StopImpersonation.php b/src/UI/View/Components/StopImpersonation.php
deleted file mode 100644
index f7bac74..0000000
--- a/src/UI/View/Components/StopImpersonation.php
+++ /dev/null
@@ -1,47 +0,0 @@
-
- */
-class StopImpersonation extends Component
-{
- public readonly string $route;
-
- public readonly string $label;
-
- public readonly string $icon;
-
- public readonly string $class;
-
- public function __construct(
- string $route = null,
- string $label = null,
- string $icon = null,
- string $class = null,
- ) {
- $this->route = $route ?? route_impersonate('stop');
- $this->label = $label ?? trans_impersonate('ui.buttons.stop.label');
- $this->icon = $icon ?? config_impersonate('buttons.stop.icon');
- $this->class = $class ?? config_impersonate('buttons.stop.class');
- }
-
- public function render(): View
- {
- return view_impersonate('components.impersonate-stop');
- }
-
- public function shouldRender(): bool
- {
- return app(ImpersonateManager::class)->canStop();
- }
-}
diff --git a/tests/Feature/Actions/EnterActionTest.php b/tests/Feature/Actions/EnterActionTest.php
index 93e7e54..5b4f627 100644
--- a/tests/Feature/Actions/EnterActionTest.php
+++ b/tests/Feature/Actions/EnterActionTest.php
@@ -12,6 +12,8 @@
use function Pest\Laravel\actingAs;
+uses()->group('actions');
+
beforeEach(function (): void {
setAuthConfig();
enableMoonShineGuard();
@@ -26,9 +28,9 @@
$action = app(EnterAction::class);
- expect($action->execute($user->getKey(), true))
+ expect($action->execute($user->getKey()))
->toBeTrue()
- ->and($action->execute($user->getKey(), true))
+ ->and($action->execute($user->getKey()))
->toBeFalse()
;
});
@@ -51,7 +53,7 @@
$action = app(EnterAction::class);
- expect($action->execute($user->getKey(), true))
+ expect($action->execute($user->getKey()))
->toBeFalse()
;
});
diff --git a/tests/Feature/Actions/StopActionTest.php b/tests/Feature/Actions/StopActionTest.php
index 7fae898..153ef77 100644
--- a/tests/Feature/Actions/StopActionTest.php
+++ b/tests/Feature/Actions/StopActionTest.php
@@ -8,6 +8,8 @@
use function Pest\Laravel\actingAs;
+uses()->group('actions');
+
beforeEach(function (): void {
setAuthConfig();
enableMoonShineGuard();
diff --git a/tests/Feature/Http/Datasets.php b/tests/Feature/Http/Datasets.php
new file mode 100644
index 0000000..b240cf8
--- /dev/null
+++ b/tests/Feature/Http/Datasets.php
@@ -0,0 +1,8 @@
+ ['get', 'enter'],
+ 'POST route' => ['post', 'enter-confirm'],
+]);
diff --git a/tests/Feature/Http/ImpersonateEnterTest.php b/tests/Feature/Http/ImpersonateEnterTest.php
index 9d60ecd..6a31456 100644
--- a/tests/Feature/Http/ImpersonateEnterTest.php
+++ b/tests/Feature/Http/ImpersonateEnterTest.php
@@ -9,14 +9,18 @@
use Jampire\MoonshineImpersonate\Tests\Stubs\Models\User;
use function Pest\Laravel\actingAs;
-use function Pest\Laravel\post;
+
+uses()->group('http');
beforeEach(function (): void {
setAuthConfig();
enableMoonShineGuard();
});
-test('privileged user can impersonate another user', function (): void {
+test('privileged user can impersonate another user', function (
+ string $routeFn,
+ string $routeName,
+): void {
Event::fake();
$user = User::factory()->create([
@@ -27,9 +31,8 @@
]);
actingAs($moonShineUser, Settings::moonShineGuard());
- $response = post(route_impersonate('enter'), [
- 'id' => $user->id,
- ]);
+
+ $response = enterResponse($routeFn, $routeName, $user->id);
$response
->assertSessionHasNoErrors()
@@ -44,6 +47,11 @@
->toBe(Settings::moonShineGuard())
->and(auth(Settings::moonShineGuard())->user()->name)
->toBe($moonShineUser->name)
+ ->and($session->get('toast'))
+ ->toMatchArray([
+ 'type' => 'success',
+ 'message' => trans_impersonate('ui.buttons.enter.message'),
+ ])
;
Event::assertDispatched(
@@ -51,75 +59,82 @@
$event->impersonator->getAuthIdentifier() === $moonShineUser->id &&
$event->impersonated->getAuthIdentifier() === $user->id
);
-});
+})->with('enter-routes');
-test('unauthorized user cannot impersonate another user', function (): void {
- $response = post(route_impersonate('enter'), [
- 'id' => User::factory()->create()->id,
- ]);
+test('unauthorized user cannot impersonate another user', function (
+ string $routeFn,
+ string $routeName,
+): void {
+ $response = enterResponse($routeFn, $routeName, User::factory()->create()->id);
$response->assertForbidden();
expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
-});
+})->with('enter-routes');
-test('regular user cannot impersonate another user', function (): void {
+test('regular user cannot impersonate another user', function (
+ string $routeFn,
+ string $routeName,
+): void {
$user = User::factory()->create();
actingAs($user, 'web');
- $response = post(route_impersonate('enter'), [
- 'id' => $user->id,
- ]);
+
+ $response = enterResponse($routeFn, $routeName, $user->id);
$response->assertForbidden();
expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
-});
+})->with('enter-routes');
-it('cannot impersonate non-existent user', function (): void {
+it('cannot impersonate non-existent user', function (
+ string $routeFn,
+ string $routeName,
+): void {
$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
- $response = post(route_impersonate('enter'), [
- 'id' => $user->id + 1,
- ]);
+ $response = enterResponse($routeFn, $routeName, $user->id + 1);
$response->assertNotFound();
expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
-});
+})->with('enter-routes');
-it('cannot impersonate if already in impersonation mode', function (): void {
+it('cannot impersonate if already in impersonation mode', function (
+ string $routeFn,
+ string $routeName,
+): void {
$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard())
->withSession([Settings::key() => $user->getKey()]);
- $response = post(route_impersonate('enter'), [
- 'id' => $user->id,
- ]);
+ $response = enterResponse($routeFn, $routeName, $user->id);
$response->assertSessionHasErrors([
'id' => trans_impersonate('validation.enter.is_impersonating'),
]);
-});
+})->with('enter-routes');
-it('cannot impersonate non-impersonated user', function (): void {
+it('cannot impersonate non-impersonated user', function (
+ string $routeFn,
+ string $routeName
+): void {
$user = User::factory()->notImpersonated()->create();
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
- $response = post(route_impersonate('enter'), [
- 'id' => $user->id,
- ]);
+
+ $response = enterResponse($routeFn, $routeName, $user->id);
$response->assertSessionHasErrors([
'id' => trans_impersonate('validation.enter.cannot_be_impersonated'),
@@ -128,16 +143,18 @@
expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
-});
+})->with('enter-routes');
-test('admin cannot impersonate with no permissions', function (): void {
+test('admin cannot impersonate with no permissions', function (
+ string $routeFn,
+ string $routeName,
+): void {
$user = User::factory()->create();
$moonShineUser = MoonshineUser::factory()->cannotImpersonate()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
- $response = post(route_impersonate('enter'), [
- 'id' => $user->id,
- ]);
+
+ $response = enterResponse($routeFn, $routeName, $user->id);
$response->assertSessionHasErrors([
'id' => trans_impersonate('validation.enter.cannot_impersonate'),
@@ -146,4 +163,4 @@
expect(session()->get(config_impersonate('key')))
->toBeEmpty()
;
-});
+})->with('enter-routes');
diff --git a/tests/Feature/Http/ImpersonateMiddlewareTest.php b/tests/Feature/Http/ImpersonateMiddlewareTest.php
index 3539ed8..c279646 100644
--- a/tests/Feature/Http/ImpersonateMiddlewareTest.php
+++ b/tests/Feature/Http/ImpersonateMiddlewareTest.php
@@ -10,7 +10,8 @@
use function Pest\Laravel\actingAs;
use function Pest\Laravel\get;
-use function Pest\Laravel\post;
+
+uses()->group('http');
beforeEach(function (): void {
setAuthConfig();
@@ -34,9 +35,10 @@
;
actingAs($moonShineUser, Settings::moonShineGuard());
- post(route_impersonate('enter'), [
- 'id' => $user->id,
- ])->assertSessionHasNoErrors();
+ get(route_impersonate('enter', [
+ config_impersonate('resource_item_key') => $user->id,
+ ]))
+ ->assertSessionHasNoErrors();
$response = get(route('test.me'));
@@ -54,9 +56,10 @@
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
- post(route_impersonate('enter'), [
- 'id' => $user->id,
- ])->assertSessionHasNoErrors();
+ get(route_impersonate('enter', [
+ config_impersonate('resource_item_key') => $user->id,
+ ]))
+ ->assertSessionHasNoErrors();
Auth::logout();
diff --git a/tests/Feature/Http/ImpersonateStopTest.php b/tests/Feature/Http/ImpersonateStopTest.php
index 0738cfd..2a22aad 100644
--- a/tests/Feature/Http/ImpersonateStopTest.php
+++ b/tests/Feature/Http/ImpersonateStopTest.php
@@ -10,7 +10,8 @@
use function Pest\Laravel\actingAs;
use function Pest\Laravel\get;
-use function Pest\Laravel\post;
+
+uses()->group('http');
beforeEach(function (): void {
setAuthConfig();
@@ -23,9 +24,9 @@
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
- post(route_impersonate('enter'), [
- 'id' => $user->id,
- ])
+ get(route_impersonate('enter', [
+ config_impersonate('resource_item_key') => $user->id,
+ ]))
->assertSessionHasNoErrors()
->assertRedirect('/');
@@ -44,6 +45,11 @@
->toBeEmpty()
->and(auth()->user())
->toBeEmpty()
+ ->and($session->get('toast'))
+ ->toMatchArray([
+ 'type' => 'success',
+ 'message' => trans_impersonate('ui.buttons.stop.message'),
+ ])
;
Event::assertDispatched(
diff --git a/tests/Feature/UI/ActionButtons/EnterImpersonationActionButtonTest.php b/tests/Feature/UI/ActionButtons/EnterImpersonationActionButtonTest.php
new file mode 100644
index 0000000..6842a17
--- /dev/null
+++ b/tests/Feature/UI/ActionButtons/EnterImpersonationActionButtonTest.php
@@ -0,0 +1,61 @@
+group('ui');
+
+it('resolves correct action button class', function (): void {
+ // don't need to use User model here
+ config(['auth.providers.users.model' => AuthUser::class]);
+
+ $user = User::factory()->create();
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard());
+
+ $actionButton = EnterImpersonationActionButton::resolve();
+
+ expect($actionButton)
+ ->toBeInstanceOf(ActionButton::class)
+ ->and($actionButton->url($user))
+ ->toBe(route_impersonate('enter', [
+ config_impersonate('resource_item_key') => $user->getKey(),
+ ]))
+ ->and($actionButton->iconValue())
+ ->toBe(config_impersonate('buttons.enter.icon'))
+ ->and($actionButton->label())
+ ->toBe(trans_impersonate('ui.buttons.enter.label'))
+ ->and($actionButton->inDropdown())
+ ->toBeFalse()
+ ;
+});
+
+it('can show action button in in-line mode', function (): void {
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard());
+
+ $actionButton = EnterImpersonationActionButton::resolve()->showInLine();
+
+ expect($actionButton->inDropdown())
+ ->toBeFalse()
+ ;
+});
+
+it('can show action button in dropdown mode', function (): void {
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard());
+
+ $actionButton = EnterImpersonationActionButton::resolve()->showInDropdown();
+
+ expect($actionButton->inDropdown())
+ ->toBeTrue()
+ ;
+});
diff --git a/tests/Feature/UI/ActionButtons/StopImpersonationActionButtonTest.php b/tests/Feature/UI/ActionButtons/StopImpersonationActionButtonTest.php
new file mode 100644
index 0000000..b23f75a
--- /dev/null
+++ b/tests/Feature/UI/ActionButtons/StopImpersonationActionButtonTest.php
@@ -0,0 +1,57 @@
+group('ui');
+
+it('resolves correct action button class', function (): void {
+ // don't need to use User model here
+ config(['auth.providers.users.model' => AuthUser::class]);
+
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard());
+
+ $actionButton = StopImpersonationActionButton::resolve();
+
+ expect($actionButton)
+ ->toBeInstanceOf(ActionButton::class)
+ ->and($actionButton->url())
+ ->toBe(route_impersonate('stop'))
+ ->and($actionButton->iconValue())
+ ->toBe(config_impersonate('buttons.stop.icon'))
+ ->and($actionButton->label())
+ ->toBe(trans_impersonate('ui.buttons.stop.label'))
+ ->and($actionButton->inDropdown())
+ ->toBeFalse()
+ ;
+});
+
+it('can show action button in in-line mode', function (): void {
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard());
+
+ $actionButton = StopImpersonationActionButton::resolve()->showInLine();
+
+ expect($actionButton->inDropdown())
+ ->toBeFalse()
+ ;
+});
+
+it('can show action button in dropdown mode', function (): void {
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard());
+
+ $actionButton = StopImpersonationActionButton::resolve()->showInDropdown();
+
+ expect($actionButton->inDropdown())
+ ->toBeTrue()
+ ;
+});
diff --git a/tests/Feature/UI/View/Components/StopImpersonationTest.php b/tests/Feature/UI/Components/StopImpersonationTest.php
similarity index 50%
rename from tests/Feature/UI/View/Components/StopImpersonationTest.php
rename to tests/Feature/UI/Components/StopImpersonationTest.php
index 3117709..b17d625 100644
--- a/tests/Feature/UI/View/Components/StopImpersonationTest.php
+++ b/tests/Feature/UI/Components/StopImpersonationTest.php
@@ -2,11 +2,18 @@
use Jampire\MoonshineImpersonate\Support\Settings;
use Jampire\MoonshineImpersonate\Tests\Stubs\Models\MoonshineUser;
-use Jampire\MoonshineImpersonate\UI\View\Components\StopImpersonation;
+use Jampire\MoonshineImpersonate\Tests\Stubs\Models\User;
+use Jampire\MoonshineImpersonate\UI\Components\StopImpersonation;
use function Pest\Laravel\actingAs;
+uses()->group('ui');
+
it('correctly renders stop button with defaults', function (): void {
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard())
+ ->withSession([config_impersonate('key') => 1]);
+
$view = $this->component(StopImpersonation::class);
$view
@@ -16,6 +23,10 @@
});
it('correctly renders stop button', function (): void {
+ $moonShineUser = MoonshineUser::factory()->create();
+ actingAs($moonShineUser, Settings::moonShineGuard())
+ ->withSession([config_impersonate('key') => 1]);
+
$view = $this->component(StopImpersonation::class, [
'label' => 'Label',
'class' => 'btn-red',
@@ -27,25 +38,28 @@
->assertSee('btn-red');
});
-it('renders stop button with permission', function (): void {
- $moonShineUser = MoonshineUser::factory()->create();
+it('does not render stop button without permissions', function (): void {
+ $user = User::factory()->create();
+ $moonShineUser = MoonshineUser::factory()->cannotImpersonate()->create();
actingAs($moonShineUser, Settings::moonShineGuard())
- ->withSession([config_impersonate('key') => 1]);
+ ->withSession([config_impersonate('key') => $user->id]);
- $component = new StopImpersonation();
+ $view = $this->component(StopImpersonation::class);
- expect($component->shouldRender())
- ->toBeTrue()
- ;
+ $view
+ ->assertDontSee(trans_impersonate('ui.buttons.stop.label'))
+ ->assertDontSee(route_impersonate('stop'))
+ ->assertDontSee(config_impersonate('buttons.stop.class'));
});
-it('does not render stop button without permissions', function (): void {
+it('does not render stop button when no one is impersonated', function (): void {
$moonShineUser = MoonshineUser::factory()->create();
actingAs($moonShineUser, Settings::moonShineGuard());
- $component = new StopImpersonation();
+ $view = $this->component(StopImpersonation::class);
- expect($component->shouldRender())
- ->toBeFalse()
- ;
+ $view
+ ->assertDontSee(trans_impersonate('ui.buttons.stop.label'))
+ ->assertDontSee(route_impersonate('stop'))
+ ->assertDontSee(config_impersonate('buttons.stop.class'));
});
diff --git a/tests/Feature/UI/ItemActions/EnterImpersonationItemActionTest.php b/tests/Feature/UI/ItemActions/EnterImpersonationItemActionTest.php
deleted file mode 100644
index 32f4021..0000000
--- a/tests/Feature/UI/ItemActions/EnterImpersonationItemActionTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
- AuthUser::class]);
-
- $user = User::factory()->create();
- $moonShineUser = MoonshineUser::factory()->create();
- actingAs($moonShineUser, Settings::moonShineGuard());
-
- $itemAction = EnterImpersonationItemAction::make()->resolve();
-
- expect($itemAction)
- ->toBeInstanceOf(ItemAction::class)
- ->and($itemAction->callback($user))
- ->toBeTrue()
- ->and($itemAction->message())
- ->toBe(trans_impersonate('ui.buttons.enter.message'))
- ->and($itemAction->iconValue())
- ->toBe(config_impersonate('buttons.enter.icon'))
- ->and($itemAction->label())
- ->toBe(trans_impersonate('ui.buttons.enter.label'))
- ->and($itemAction->inDropdown())
- ->toBeTrue()
- ;
-});
-
-it('can show item action in in-line mode', function (): void {
- $moonShineUser = MoonshineUser::factory()->create();
- actingAs($moonShineUser, Settings::moonShineGuard());
-
- $itemAction = EnterImpersonationItemAction::make()->resolve()->showInLine();
-
- expect($itemAction->inDropdown())
- ->toBeFalse()
- ;
-});
-
-it('can show confirmation dialog for this item action', function (): void {
- $moonShineUser = MoonshineUser::factory()->create();
- actingAs($moonShineUser, Settings::moonShineGuard());
-
- $itemAction = EnterImpersonationItemAction::make()->resolve()->withConfirm();
-
- expect($itemAction->isConfirmed())
- ->toBeTrue()
- ;
-});
diff --git a/tests/Pest.php b/tests/Pest.php
index bd45fd8..0f6e092 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -4,11 +4,15 @@
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
+use Illuminate\Testing\TestResponse;
use Jampire\MoonshineImpersonate\Support\Settings;
use Jampire\MoonshineImpersonate\Tests\Stubs\Models\MoonshineUser;
use Jampire\MoonshineImpersonate\Tests\Stubs\Models\User;
use Jampire\MoonshineImpersonate\Tests\TestCase;
+use function Pest\Laravel\get;
+use function Pest\Laravel\post;
+
/*
|--------------------------------------------------------------------------
| Test Case
@@ -113,3 +117,18 @@ function registerHomePage(): void
->name('me');
});
}
+
+function enterResponse(string $routeFn, string $routeName, int $id): TestResponse
+{
+ if ($routeFn === 'get') {
+ $response = get(route_impersonate($routeName, [
+ config_impersonate('resource_item_key') => $id,
+ ]));
+ } else {
+ $response = post(route_impersonate($routeName, [
+ config_impersonate('resource_item_key') => $id,
+ ]));
+ }
+
+ return $response;
+}
diff --git a/tests/Stubs/Models/User.php b/tests/Stubs/Models/User.php
index 9be670b..997512e 100644
--- a/tests/Stubs/Models/User.php
+++ b/tests/Stubs/Models/User.php
@@ -9,7 +9,7 @@
use Illuminate\Support\Str;
use Jampire\MoonshineImpersonate\Services\Contracts\BeImpersonable;
use Jampire\MoonshineImpersonate\Tests\Stubs\Database\Factories\UserFactory;
-use MoonShine\Traits\Models\HasMoonShineChangeLog;
+use MoonShine\ChangeLog\Traits\HasChangeLog;
/**
* Class User
@@ -19,7 +19,7 @@
class User extends BaseUser implements BeImpersonable
{
use HasFactory;
- use HasMoonShineChangeLog;
+ use HasChangeLog;
protected $table = 'users';
diff --git a/tests/Stubs/resources/views/components/link-button.blade.php b/tests/Stubs/resources/views/components/link-button.blade.php
new file mode 100644
index 0000000..09d3385
--- /dev/null
+++ b/tests/Stubs/resources/views/components/link-button.blade.php
@@ -0,0 +1,14 @@
+@props([
+ 'icon' => false,
+ 'filled' => false,
+])
+class(['btn', 'btn-primary' => $filled]) }}>
+ @if($icon)
+
+ @endif
+
+ {{ $slot }}
+
diff --git a/tests/Stubs/resources/views/components/link.blade.php b/tests/Stubs/resources/views/components/link.blade.php
deleted file mode 100644
index 88c6b31..0000000
--- a/tests/Stubs/resources/views/components/link.blade.php
+++ /dev/null
@@ -1,12 +0,0 @@
-@props([
- 'icon' => false,
- 'filled' => false
-])
-class(['btn', 'btn-primary' => $filled]) }}>
-
-
- {{ $slot }}
-
diff --git a/tests/Unit/HelpersTest.php b/tests/Unit/HelpersTest.php
new file mode 100644
index 0000000..0cf5e84
--- /dev/null
+++ b/tests/Unit/HelpersTest.php
@@ -0,0 +1,52 @@
+toBe(route(Settings::ALIAS.'.enter'))
+ ;
+});
+
+test('config_impersonate() function returns config option', function (): void {
+ expect(config_impersonate('key'))
+ ->toBe(config(Settings::ALIAS.'.key'))
+ ;
+});
+
+test('trans_impersonate() function returns translation', function (): void {
+ expect(trans_impersonate('ui.buttons.enter.message'))
+ ->toBe(trans(Settings::ALIAS.'::ui.buttons.enter.message'))
+ ;
+});
+
+test('view_impersonate() function returns View', function (): void {
+ expect(view_impersonate('components.impersonate-stop')::class)
+ ->toBe(view(Settings::ALIAS.'::components.impersonate-stop')::class)
+ ;
+});
+
+test('toast_if() function flashes message in a session', function (): void {
+ $message = 'hello';
+ toast_if(true, $message);
+
+ $session = session();
+ expect($session->has('toast'))
+ ->toBeTrue()
+ ->and($session->get('toast'))
+ ->toMatchArray([
+ 'type' => 'info',
+ 'message' => $message,
+ ])
+ ;
+});
+
+test('toast_if() function does not flash message in a session', function (): void {
+ toast_if(false, 'hello');
+
+ expect(session()->has('toast'))
+ ->toBeFalse()
+ ;
+});
diff --git a/tests/Unit/RouteTest.php b/tests/Unit/RouteTest.php
new file mode 100644
index 0000000..160cb9f
--- /dev/null
+++ b/tests/Unit/RouteTest.php
@@ -0,0 +1,35 @@
+group('http');
+
+test('GET enter route is correct', function (): void {
+ $route = route_impersonate('enter', [
+ config_impersonate('resource_item_key') => 123,
+ ]);
+
+ expect(Str::endsWith($route, config('moonshine.route.prefix').'/impersonate/enter?resourceItem=123'))
+ ->toBeTrue()
+ ;
+});
+
+test('POST enter route is correct', function (): void {
+ $route = route_impersonate('enter-confirm', [
+ config_impersonate('resource_item_key') => 123,
+ ]);
+
+ expect(Str::endsWith($route, config('moonshine.route.prefix').'/impersonate/enter?resourceItem=123'))
+ ->toBeTrue()
+ ;
+});
+
+test('GET stop route is correct', function (): void {
+ $route = route_impersonate('stop');
+
+ expect(Str::endsWith($route, config('moonshine.route.prefix').'/impersonate/stop'))
+ ->toBeTrue()
+ ;
+});
diff --git a/tests/Unit/StrictTypesTest.php b/tests/Unit/StrictTypesTest.php
new file mode 100644
index 0000000..f807023
--- /dev/null
+++ b/tests/Unit/StrictTypesTest.php
@@ -0,0 +1,10 @@
+group('arch', 'strict');
+
+arch('strict')
+ ->expect('Jampire\MoonshineImpersonate')
+ ->toUseStrictTypes()
+;