Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="APP_KEY" value="base64:2fl+KtvkdphvQo6zK/ZBmHVGXKqXJ6X6qE8z0vqKq0k="/>
<env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
Expand Down
7 changes: 0 additions & 7 deletions tests/Feature/ExampleTest.php

This file was deleted.

18 changes: 18 additions & 0 deletions tests/Feature/Filament/Auth/DashboardAccessTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

use App\Enums\RolesEnum;

describe('dashboard access', function () {
it('redirects guests to the login page', function () {
$this->get('/dashboard')
->assertRedirect();
});

it('allows authenticated users to access the dashboard', function () {
$user = createUserWithRole(RolesEnum::EDITOR);

$this->actingAs($user)
->get('/dashboard')
->assertSuccessful();
});
});
58 changes: 58 additions & 0 deletions tests/Feature/Filament/Users/InviteUserActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

use App\Enums\RolesEnum;
use App\Filament\Resources\Users\Actions\InviteUserAction;
use App\Filament\Resources\Users\Pages\ListUsers;
use App\Mail\InviteUser;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Livewire\Livewire;

describe('InviteUserAction', function () {
beforeEach(function () {
Mail::fake();
seedRolesAndPermissions();
});

it('creates an invited user and sends the invitation email', function () {
InviteUserAction::handle([
'name' => 'Invited User',
'email' => 'invited@example.com',
'role' => RolesEnum::EDITOR->value,
]);

$user = User::query()->where('email', 'invited@example.com')->first();

expect($user)->not->toBeNull()
->and($user->name)->toBe('Invited User')
->and($user->password)->toBeNull()
->and($user->invite_token)->not->toBeEmpty()
->and($user->hasRole(RolesEnum::EDITOR))->toBeTrue();

Mail::assertSent(InviteUser::class, function (InviteUser $mail) use ($user) {
return $mail->hasTo('invited@example.com')
&& $mail->user->is($user);
});
});

it('can invite a user from the users list page', function () {
$admin = createUserWithRole(RolesEnum::ADMIN);

actingAsFilamentUser($admin);

Livewire::test(ListUsers::class)
->callAction('invite-user', data: [
'name' => 'Filament Invitee',
'email' => 'filament-invitee@example.com',
'role' => RolesEnum::ADMIN->value,
])
->assertNotified();

$user = User::query()->where('email', 'filament-invitee@example.com')->first();

expect($user)->not->toBeNull()
->and($user->hasRole(RolesEnum::ADMIN))->toBeTrue();

Mail::assertSent(InviteUser::class);
});
});
34 changes: 34 additions & 0 deletions tests/Feature/Filament/Users/ListUsersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use App\Enums\RolesEnum;
use App\Filament\Resources\Users\Pages\ListUsers;
use App\Models\User;
use Livewire\Livewire;

describe('ListUsers', function () {
it('allows an admin to view the users table', function () {
$admin = createUserWithRole(RolesEnum::ADMIN);
$users = User::factory()->count(2)->create();

actingAsFilamentUser($admin);

Livewire::test(ListUsers::class)
->assertSuccessful()
->loadTable()
->assertCanSeeTableRecords($users);
});

it('can search users by name', function () {
$admin = createUserWithRole(RolesEnum::ADMIN);
$matchingUser = User::factory()->create(['name' => 'Unique Search Name']);
$otherUser = User::factory()->create(['name' => 'Someone Else']);

actingAsFilamentUser($admin);

Livewire::test(ListUsers::class)
->loadTable()
->searchTable('Unique Search Name')
->assertCanSeeTableRecords([$matchingUser])
->assertCanNotSeeTableRecords([$otherUser]);
});
});
7 changes: 7 additions & 0 deletions tests/Feature/Http/WelcomePageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

it('displays the welcome page', function () {
$this->get('/')
->assertSuccessful()
->assertViewIs('welcome');
});
35 changes: 35 additions & 0 deletions tests/Feature/Mail/InviteUserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use App\Mail\InviteUser;
use App\Models\User;
use Illuminate\Support\Str;

describe('InviteUser mailable', function () {
beforeEach(function () {
seedRolesAndPermissions();
});

it('uses the invitation subject', function () {
$user = User::factory()->make([
'invite_token' => Str::random(60),
]);

expect((new InviteUser($user))->envelope()->subject)
->toBe('You have been invited to join the team!');
});

it('includes a signed registration url with the invite token', function () {
$user = User::factory()->create([
'invite_token' => 'test-invite-token',
]);

$mailable = new InviteUser($user);
$content = $mailable->content();

expect($content->markdown)->toBe('mail.auth.invite-user')
->and($content->with['acceptUrl'])
->toContain('invite/register')
->toContain('token=test-invite-token')
->toContain('signature=');
});
});
82 changes: 82 additions & 0 deletions tests/Feature/Models/UserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

use App\Enums\RolesEnum;
use App\Models\User;
use Filament\Facades\Filament;

describe('User model', function () {
beforeEach(function () {
seedRolesAndPermissions();
});

it('allows all users to access the dashboard panel', function () {
$user = User::factory()->create();

expect($user->canAccessPanel(Filament::getPanel('dashboard')))->toBeTrue();
});

it('uses the user email as the app authentication holder name', function () {
$user = User::factory()->create([
'email' => 'jane@example.com',
]);

expect($user->getAppAuthenticationHolderName())->toBe('jane@example.com');
});

it('generates a ui-avatars url for the filament avatar', function () {
$user = User::factory()->create([
'name' => 'Jane Doe',
]);

expect($user->getFilamentAvatarUrl())
->toStartWith('https://ui-avatars.com/api/')
->toContain('name=');
});

it('allows super admins to impersonate other users', function () {
$superAdmin = createUserWithRole(RolesEnum::SUPER_ADMIN);

expect($superAdmin->canImpersonate())->toBeTrue();
});

it('allows users with impersonate permission to impersonate', function () {
$admin = createUserWithRole(RolesEnum::ADMIN);

expect($admin->canImpersonate())->toBeTrue();
});

it('does not allow editors without impersonate permission to impersonate', function () {
$editor = createUserWithRole(RolesEnum::EDITOR);

expect($editor->canImpersonate())->toBeFalse();
});

it('does not allow super admins to be impersonated', function () {
$superAdmin = createUserWithRole(RolesEnum::SUPER_ADMIN);

expect($superAdmin->canBeImpersonated())->toBeFalse();
});

it('allows non-super-admin users to be impersonated', function () {
$admin = createUserWithRole(RolesEnum::ADMIN);

expect($admin->canBeImpersonated())->toBeTrue();
});

it('persists app authentication secrets', function () {
$user = User::factory()->create();

$user->saveAppAuthenticationSecret('test-secret');

expect($user->fresh()->getAppAuthenticationSecret())->toBe('test-secret');
});

it('persists app authentication recovery codes', function () {
$user = User::factory()->create();
$codes = ['code-one', 'code-two'];

$user->saveAppAuthenticationRecoveryCodes($codes);

expect($user->fresh()->getAppAuthenticationRecoveryCodes())->toBe($codes);
});
});
30 changes: 30 additions & 0 deletions tests/Feature/Observers/UserObserverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

use App\Enums\RolesEnum;
use App\Models\User;

describe('UserObserver', function () {
beforeEach(function () {
seedRolesAndPermissions();
});

it('assigns the editor role when a user is created without a role', function () {
$user = User::factory()->create();

expect($user->hasRole(RolesEnum::EDITOR))->toBeTrue();
});

it('does not assign the editor role when the user already has a role', function () {
$user = User::withoutEvents(function () {
$user = User::factory()->create();
$user->assignRole(RolesEnum::ADMIN);

return $user;
});

(new \App\Observers\UserObserver)->created($user);

expect($user->hasRole(RolesEnum::ADMIN))->toBeTrue()
->and($user->hasRole(RolesEnum::EDITOR))->toBeFalse();
});
});
54 changes: 31 additions & 23 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
<?php

/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind a different classes or traits.
|
*/
use App\Enums\RolesEnum;
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->extend(Tests\TestCase::class)
// ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
->in('Unit');

pest()->extend(Tests\TestCase::class)
->use(RefreshDatabase::class)
->in('Feature');

/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/

expect()->extend('toBeOne', function () {
Expand All @@ -34,14 +27,29 @@
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/

function something()
function seedRolesAndPermissions(): void
{
test()->seed(RolesAndPermissionsSeeder::class);
}

function createUserWithRole(RolesEnum $role, array $attributes = []): User
{
// ..
seedRolesAndPermissions();

$user = User::factory()->create($attributes);

$user->assignRole($role);

return $user;
}

function actingAsFilamentUser(User $user): User
{
test()->actingAs($user);

Filament::setCurrentPanel(Filament::getPanel('dashboard'));

return $user;
}
28 changes: 28 additions & 0 deletions tests/Unit/Enums/RolesEnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use App\Enums\RolesEnum;

describe('RolesEnum', function () {
it('has expected cases', function () {
expect(RolesEnum::cases())->toHaveCount(3)
->and(RolesEnum::SUPER_ADMIN->value)->toBe('super-admin')
->and(RolesEnum::ADMIN->value)->toBe('admin')
->and(RolesEnum::EDITOR->value)->toBe('editor');
});

it('returns human-readable labels', function (RolesEnum $role, string $label) {
expect($role->getLabel())->toBe($label);
})->with([
'super admin' => [RolesEnum::SUPER_ADMIN, 'Super Admin'],
'admin' => [RolesEnum::ADMIN, 'Admin'],
'editor' => [RolesEnum::EDITOR, 'Editor'],
]);

it('returns filament colors', function (RolesEnum $role, string $color) {
expect($role->getColor())->toBe($color);
})->with([
'super admin' => [RolesEnum::SUPER_ADMIN, 'primary'],
'admin' => [RolesEnum::ADMIN, 'success'],
'editor' => [RolesEnum::EDITOR, 'warning'],
]);
});
Loading