diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 8158baec6..386a51996 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -17,6 +17,7 @@ class InstallCommand extends Command */ protected $signature = 'jetstream:install {stack : The development stack that should be installed} {--teams : Indicates if team support should be installed} + {--pest : Indicates if Pest should be installed} {--composer=global : Absolute path to the Composer binary which should be used to install packages}'; /** @@ -70,11 +71,21 @@ public function handle() } // Tests... - copy(__DIR__.'/../../stubs/tests/AuthenticationTest.php', base_path('tests/Feature/AuthenticationTest.php')); - copy(__DIR__.'/../../stubs/tests/EmailVerificationTest.php', base_path('tests/Feature/EmailVerificationTest.php')); - copy(__DIR__.'/../../stubs/tests/PasswordConfirmationTest.php', base_path('tests/Feature/PasswordConfirmationTest.php')); - copy(__DIR__.'/../../stubs/tests/PasswordResetTest.php', base_path('tests/Feature/PasswordResetTest.php')); - copy(__DIR__.'/../../stubs/tests/RegistrationTest.php', base_path('tests/Feature/RegistrationTest.php')); + $stubs = $this->getTestStubsPath(); + + if ($this->option('pest')) { + $this->requireComposerPackages('pestphp/pest:^1.16', 'pestphp/pest-plugin-laravel:^1.1'); + + copy($stubs.'/Pest.php', base_path('tests/Pest.php')); + copy($stubs.'/ExampleTest.php', base_path('tests/Feature/ExampleTest.php')); + copy($stubs.'/ExampleUnitTest.php', base_path('tests/Unit/ExampleTest.php')); + } + + copy($stubs.'/AuthenticationTest.php', base_path('tests/Feature/AuthenticationTest.php')); + copy($stubs.'/EmailVerificationTest.php', base_path('tests/Feature/EmailVerificationTest.php')); + copy($stubs.'/PasswordConfirmationTest.php', base_path('tests/Feature/PasswordConfirmationTest.php')); + copy($stubs.'/PasswordResetTest.php', base_path('tests/Feature/PasswordResetTest.php')); + copy($stubs.'/RegistrationTest.php', base_path('tests/Feature/RegistrationTest.php')); } /** @@ -197,14 +208,16 @@ protected function installLivewireStack() copy(__DIR__.'/../../stubs/livewire/resources/js/app.js', resource_path('js/app.js')); // Tests... - copy(__DIR__.'/../../stubs/tests/livewire/ApiTokenPermissionsTest.php', base_path('tests/Feature/ApiTokenPermissionsTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/BrowserSessionsTest.php', base_path('tests/Feature/BrowserSessionsTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/CreateApiTokenTest.php', base_path('tests/Feature/CreateApiTokenTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/DeleteAccountTest.php', base_path('tests/Feature/DeleteAccountTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/DeleteApiTokenTest.php', base_path('tests/Feature/DeleteApiTokenTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/ProfileInformationTest.php', base_path('tests/Feature/ProfileInformationTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/TwoFactorAuthenticationSettingsTest.php', base_path('tests/Feature/TwoFactorAuthenticationSettingsTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/UpdatePasswordTest.php', base_path('tests/Feature/UpdatePasswordTest.php')); + $stubs = $this->getTestStubsPath(); + + copy($stubs.'/livewire/ApiTokenPermissionsTest.php', base_path('tests/Feature/ApiTokenPermissionsTest.php')); + copy($stubs.'/livewire/BrowserSessionsTest.php', base_path('tests/Feature/BrowserSessionsTest.php')); + copy($stubs.'/livewire/CreateApiTokenTest.php', base_path('tests/Feature/CreateApiTokenTest.php')); + copy($stubs.'/livewire/DeleteAccountTest.php', base_path('tests/Feature/DeleteAccountTest.php')); + copy($stubs.'/livewire/DeleteApiTokenTest.php', base_path('tests/Feature/DeleteApiTokenTest.php')); + copy($stubs.'/livewire/ProfileInformationTest.php', base_path('tests/Feature/ProfileInformationTest.php')); + copy($stubs.'/livewire/TwoFactorAuthenticationSettingsTest.php', base_path('tests/Feature/TwoFactorAuthenticationSettingsTest.php')); + copy($stubs.'/livewire/UpdatePasswordTest.php', base_path('tests/Feature/UpdatePasswordTest.php')); // Teams... if ($this->option('teams')) { @@ -230,13 +243,15 @@ protected function installLivewireTeamStack() (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/livewire/resources/views/teams', resource_path('views/teams')); // Tests... - copy(__DIR__.'/../../stubs/tests/livewire/CreateTeamTest.php', base_path('tests/Feature/CreateTeamTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/DeleteTeamTest.php', base_path('tests/Feature/DeleteTeamTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/InviteTeamMemberTest.php', base_path('tests/Feature/InviteTeamMemberTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/LeaveTeamTest.php', base_path('tests/Feature/LeaveTeamTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/RemoveTeamMemberTest.php', base_path('tests/Feature/RemoveTeamMemberTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/UpdateTeamMemberRoleTest.php', base_path('tests/Feature/UpdateTeamMemberRoleTest.php')); - copy(__DIR__.'/../../stubs/tests/livewire/UpdateTeamNameTest.php', base_path('tests/Feature/UpdateTeamNameTest.php')); + $stubs = $this->getTestStubsPath(); + + copy($stubs.'/livewire/CreateTeamTest.php', base_path('tests/Feature/CreateTeamTest.php')); + copy($stubs.'/livewire/DeleteTeamTest.php', base_path('tests/Feature/DeleteTeamTest.php')); + copy($stubs.'/livewire/InviteTeamMemberTest.php', base_path('tests/Feature/InviteTeamMemberTest.php')); + copy($stubs.'/livewire/LeaveTeamTest.php', base_path('tests/Feature/LeaveTeamTest.php')); + copy($stubs.'/livewire/RemoveTeamMemberTest.php', base_path('tests/Feature/RemoveTeamMemberTest.php')); + copy($stubs.'/livewire/UpdateTeamMemberRoleTest.php', base_path('tests/Feature/UpdateTeamMemberRoleTest.php')); + copy($stubs.'/livewire/UpdateTeamNameTest.php', base_path('tests/Feature/UpdateTeamNameTest.php')); $this->ensureApplicationIsTeamCompatible(); } @@ -373,14 +388,16 @@ protected function installInertiaStack() // static::flushNodeModules(); // Tests... - copy(__DIR__.'/../../stubs/tests/inertia/ApiTokenPermissionsTest.php', base_path('tests/Feature/ApiTokenPermissionsTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/BrowserSessionsTest.php', base_path('tests/Feature/BrowserSessionsTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/CreateApiTokenTest.php', base_path('tests/Feature/CreateApiTokenTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/DeleteAccountTest.php', base_path('tests/Feature/DeleteAccountTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/DeleteApiTokenTest.php', base_path('tests/Feature/DeleteApiTokenTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/ProfileInformationTest.php', base_path('tests/Feature/ProfileInformationTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/TwoFactorAuthenticationSettingsTest.php', base_path('tests/Feature/TwoFactorAuthenticationSettingsTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/UpdatePasswordTest.php', base_path('tests/Feature/UpdatePasswordTest.php')); + $stubs = $this->getTestStubsPath(); + + copy($stubs.'/inertia/ApiTokenPermissionsTest.php', base_path('tests/Feature/ApiTokenPermissionsTest.php')); + copy($stubs.'/inertia/BrowserSessionsTest.php', base_path('tests/Feature/BrowserSessionsTest.php')); + copy($stubs.'/inertia/CreateApiTokenTest.php', base_path('tests/Feature/CreateApiTokenTest.php')); + copy($stubs.'/inertia/DeleteAccountTest.php', base_path('tests/Feature/DeleteAccountTest.php')); + copy($stubs.'/inertia/DeleteApiTokenTest.php', base_path('tests/Feature/DeleteApiTokenTest.php')); + copy($stubs.'/inertia/ProfileInformationTest.php', base_path('tests/Feature/ProfileInformationTest.php')); + copy($stubs.'/inertia/TwoFactorAuthenticationSettingsTest.php', base_path('tests/Feature/TwoFactorAuthenticationSettingsTest.php')); + copy($stubs.'/inertia/UpdatePasswordTest.php', base_path('tests/Feature/UpdatePasswordTest.php')); // Teams... if ($this->option('teams')) { @@ -406,13 +423,15 @@ protected function installInertiaTeamStack() (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia/resources/js/Pages/Teams', resource_path('js/Pages/Teams')); // Tests... - copy(__DIR__.'/../../stubs/tests/inertia/CreateTeamTest.php', base_path('tests/Feature/CreateTeamTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/DeleteTeamTest.php', base_path('tests/Feature/DeleteTeamTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/InviteTeamMemberTest.php', base_path('tests/Feature/InviteTeamMemberTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/LeaveTeamTest.php', base_path('tests/Feature/LeaveTeamTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/RemoveTeamMemberTest.php', base_path('tests/Feature/RemoveTeamMemberTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/UpdateTeamMemberRoleTest.php', base_path('tests/Feature/UpdateTeamMemberRoleTest.php')); - copy(__DIR__.'/../../stubs/tests/inertia/UpdateTeamNameTest.php', base_path('tests/Feature/UpdateTeamNameTest.php')); + $stubs = $this->getTestStubsPath(); + + copy($stubs.'/inertia/CreateTeamTest.php', base_path('tests/Feature/CreateTeamTest.php')); + copy($stubs.'/inertia/DeleteTeamTest.php', base_path('tests/Feature/DeleteTeamTest.php')); + copy($stubs.'/inertia/InviteTeamMemberTest.php', base_path('tests/Feature/InviteTeamMemberTest.php')); + copy($stubs.'/inertia/LeaveTeamTest.php', base_path('tests/Feature/LeaveTeamTest.php')); + copy($stubs.'/inertia/RemoveTeamMemberTest.php', base_path('tests/Feature/RemoveTeamMemberTest.php')); + copy($stubs.'/inertia/UpdateTeamMemberRoleTest.php', base_path('tests/Feature/UpdateTeamMemberRoleTest.php')); + copy($stubs.'/inertia/UpdateTeamNameTest.php', base_path('tests/Feature/UpdateTeamNameTest.php')); $this->ensureApplicationIsTeamCompatible(); } @@ -512,6 +531,18 @@ protected function installMiddlewareAfter($after, $name, $group = 'web') } } + /** + * Returns the path to the correct test stubs. + * + * @return string + */ + protected function getTestStubsPath() + { + return $this->option('pest') + ? __DIR__.'/../../stubs/pest-tests' + : __DIR__.'/../../stubs/tests'; + } + /** * Installs the given Composer Packages into the application. * diff --git a/stubs/pest-tests/AuthenticationTest.php b/stubs/pest-tests/AuthenticationTest.php new file mode 100644 index 000000000..9dd64e119 --- /dev/null +++ b/stubs/pest-tests/AuthenticationTest.php @@ -0,0 +1,33 @@ +get('/login'); + + $response->assertStatus(200); +}); + +test('users can authenticate using the login screen', function () { + $user = User::factory()->create(); + + $response = $this->post('/login', [ + 'email' => $user->email, + 'password' => 'password', + ]); + + $this->assertAuthenticated(); + $response->assertRedirect(RouteServiceProvider::HOME); +}); + +test('users can not authenticate with invalid_password', function () { + $user = User::factory()->create(); + + $this->post('/login', [ + 'email' => $user->email, + 'password' => 'wrong-password', + ]); + + $this->assertGuest(); +}); diff --git a/stubs/pest-tests/EmailVerificationTest.php b/stubs/pest-tests/EmailVerificationTest.php new file mode 100644 index 000000000..30b68aff0 --- /dev/null +++ b/stubs/pest-tests/EmailVerificationTest.php @@ -0,0 +1,61 @@ +withPersonalTeam()->create([ + 'email_verified_at' => null, + ]); + + $response = $this->actingAs($user)->get('/email/verify'); + + $response->assertStatus(200); +})->skip(function () { + return ! Features::enabled(Features::emailVerification()); +}, 'Email verification not enabled.'); + +test('email can be verified', function () { + Event::fake(); + + $user = User::factory()->create([ + 'email_verified_at' => null, + ]); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); + + $response = $this->actingAs($user)->get($verificationUrl); + + Event::assertDispatched(Verified::class); + + expect($user->fresh()->hasVerifiedEmail())->toBeTrue(); + $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1'); +})->skip(function () { + return ! Features::enabled(Features::emailVerification()); +}, 'Email verification not enabled.'); + +test('email can not verified with invalid hash', function () { + $user = User::factory()->create([ + 'email_verified_at' => null, + ]); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); + + $this->actingAs($user)->get($verificationUrl); + + expect($user->fresh()->hasVerifiedEmail())->toBeFalse(); +})->skip(function () { + return ! Features::enabled(Features::emailVerification()); +}, 'Email verification not enabled.'); diff --git a/stubs/pest-tests/ExampleTest.php b/stubs/pest-tests/ExampleTest.php new file mode 100644 index 000000000..b46239fd0 --- /dev/null +++ b/stubs/pest-tests/ExampleTest.php @@ -0,0 +1,7 @@ +get('/'); + + $response->assertStatus(200); +}); diff --git a/stubs/pest-tests/ExampleUnitTest.php b/stubs/pest-tests/ExampleUnitTest.php new file mode 100644 index 000000000..61cd84c32 --- /dev/null +++ b/stubs/pest-tests/ExampleUnitTest.php @@ -0,0 +1,5 @@ +toBeTrue(); +}); diff --git a/stubs/pest-tests/PasswordConfirmationTest.php b/stubs/pest-tests/PasswordConfirmationTest.php new file mode 100644 index 000000000..009a4629d --- /dev/null +++ b/stubs/pest-tests/PasswordConfirmationTest.php @@ -0,0 +1,35 @@ +withPersonalTeam()->create() + : User::factory()->create(); + + $response = $this->actingAs($user)->get('/user/confirm-password'); + + $response->assertStatus(200); +}); + +test('password can be confirmed', function () { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/user/confirm-password', [ + 'password' => 'password', + ]); + + $response->assertRedirect(); + $response->assertSessionHasNoErrors(); +}); + +test('password is not confirmed with invalid password', function () { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/user/confirm-password', [ + 'password' => 'wrong-password', + ]); + + $response->assertSessionHasErrors(); +}); diff --git a/stubs/pest-tests/PasswordResetTest.php b/stubs/pest-tests/PasswordResetTest.php new file mode 100644 index 000000000..334e732c6 --- /dev/null +++ b/stubs/pest-tests/PasswordResetTest.php @@ -0,0 +1,73 @@ +get('/forgot-password'); + + $response->assertStatus(200); +})->skip(function () { + return ! Features::enabled(Features::updatePasswords()); +}, 'Password updates are not enabled.'); + +test('reset password link can be requested', function () { + Notification::fake(); + + $user = User::factory()->create(); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + Notification::assertSentTo($user, ResetPassword::class); +})->skip(function () { + return ! Features::enabled(Features::updatePasswords()); +}, 'Password updates are not enabled.'); + +test('reset password screen can be rendered', function () { + Notification::fake(); + + $user = User::factory()->create(); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $response = $this->get('/reset-password/'.$notification->token); + + $response->assertStatus(200); + + return true; + }); +})->skip(function () { + return ! Features::enabled(Features::updatePasswords()); +}, 'Password updates are not enabled.'); + +test('password can be reset with valid token', function () { + Notification::fake(); + + $user = User::factory()->create(); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $response = $this->post('/reset-password', [ + 'token' => $notification->token, + 'email' => $user->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + $response->assertSessionHasNoErrors(); + + return true; + }); +})->skip(function () { + return ! Features::enabled(Features::updatePasswords()); +}, 'Password updates are not enabled.'); diff --git a/stubs/pest-tests/Pest.php b/stubs/pest-tests/Pest.php new file mode 100644 index 000000000..e2eb38087 --- /dev/null +++ b/stubs/pest-tests/Pest.php @@ -0,0 +1,48 @@ +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 () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| 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() +{ + // .. +} diff --git a/stubs/pest-tests/RegistrationTest.php b/stubs/pest-tests/RegistrationTest.php new file mode 100644 index 000000000..856a3053d --- /dev/null +++ b/stubs/pest-tests/RegistrationTest.php @@ -0,0 +1,36 @@ +get('/register'); + + $response->assertStatus(200); +})->skip(function () { + return ! Features::enabled(Features::registration()); +}, 'Registration support is not enabled.'); + +test('registration screen cannot be rendered if support is disabled', function () { + $response = $this->get('/register'); + + $response->assertStatus(404); +})->skip(function () { + return Features::enabled(Features::registration()); +}, 'Registration support is enabled.'); + +test('new users can register', function () { + $response = $this->post('/register', [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'password' => 'password', + 'password_confirmation' => 'password', + 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), + ]); + + $this->assertAuthenticated(); + $response->assertRedirect(RouteServiceProvider::HOME); +})->skip(function () { + return ! Features::enabled(Features::registration()); +}, 'Registration support is not enabled.'); diff --git a/stubs/pest-tests/inertia/ApiTokenPermissionsTest.php b/stubs/pest-tests/inertia/ApiTokenPermissionsTest.php new file mode 100644 index 000000000..d982f3caa --- /dev/null +++ b/stubs/pest-tests/inertia/ApiTokenPermissionsTest.php @@ -0,0 +1,34 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => ['create', 'read'], + ]); + + $response = $this->put('/user/api-tokens/'.$token->id, [ + 'name' => $token->name, + 'permissions' => [ + 'delete', + 'missing-permission', + ], + ]); + + expect($user->fresh()->tokens->first()) + ->can('delete')->toBeTrue() + ->can('read')->toBeFalse() + ->can('missing-permission')->toBeFalse(); +})->skip(function () { + return ! Features::hasApiFeatures(); +}, 'API support is not enabled.'); diff --git a/stubs/pest-tests/inertia/BrowserSessionsTest.php b/stubs/pest-tests/inertia/BrowserSessionsTest.php new file mode 100644 index 000000000..e704bdeac --- /dev/null +++ b/stubs/pest-tests/inertia/BrowserSessionsTest.php @@ -0,0 +1,13 @@ +actingAs($user = User::factory()->create()); + + $response = $this->delete('/user/other-browser-sessions', [ + 'password' => 'password', + ]); + + $response->assertSessionHasNoErrors(); +}); diff --git a/stubs/pest-tests/inertia/CreateApiTokenTest.php b/stubs/pest-tests/inertia/CreateApiTokenTest.php new file mode 100644 index 000000000..c86e898a5 --- /dev/null +++ b/stubs/pest-tests/inertia/CreateApiTokenTest.php @@ -0,0 +1,28 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $response = $this->post('/user/api-tokens', [ + 'name' => 'Test Token', + 'permissions' => [ + 'read', + 'update', + ], + ]); + + expect($user->fresh()->tokens)->toHaveCount(1); + expect($user->fresh()->tokens->first()) + ->name->toEqual('Test Token') + ->can('read')->toBeTrue() + ->can('delete')->toBeFalse(); +})->skip(function () { + return ! Features::hasApiFeatures(); +}, 'API support is not enabled.'); diff --git a/stubs/pest-tests/inertia/CreateTeamTest.php b/stubs/pest-tests/inertia/CreateTeamTest.php new file mode 100644 index 000000000..530cd0dca --- /dev/null +++ b/stubs/pest-tests/inertia/CreateTeamTest.php @@ -0,0 +1,14 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $response = $this->post('/teams', [ + 'name' => 'Test Team', + ]); + + expect($user->fresh()->ownedTeams)->toHaveCount(2); + expect($user->fresh()->ownedTeams()->latest('id')->first()->name)->toEqual('Test Team'); +}); diff --git a/stubs/pest-tests/inertia/DeleteAccountTest.php b/stubs/pest-tests/inertia/DeleteAccountTest.php new file mode 100644 index 000000000..6d3cadd55 --- /dev/null +++ b/stubs/pest-tests/inertia/DeleteAccountTest.php @@ -0,0 +1,28 @@ +actingAs($user = User::factory()->create()); + + $response = $this->delete('/user', [ + 'password' => 'password', + ]); + + expect($user->fresh())->toBeNull(); +})->skip(function () { + return ! Features::hasAccountDeletionFeatures(); +}, 'Account deletion is not enabled.'); + +test('correct password must be provided before account can be deleted', function () { + $this->actingAs($user = User::factory()->create()); + + $response = $this->delete('/user', [ + 'password' => 'wrong-password', + ]); + + expect($user->fresh())->not->toBeNull(); +})->skip(function () { + return ! Features::hasAccountDeletionFeatures(); +}, 'Account deletion is not enabled.'); diff --git a/stubs/pest-tests/inertia/DeleteApiTokenTest.php b/stubs/pest-tests/inertia/DeleteApiTokenTest.php new file mode 100644 index 000000000..e1f0dfe62 --- /dev/null +++ b/stubs/pest-tests/inertia/DeleteApiTokenTest.php @@ -0,0 +1,25 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => ['create', 'read'], + ]); + + $response = $this->delete('/user/api-tokens/'.$token->id); + + expect($user->fresh()->tokens)->toHaveCount(0); +})->skip(function () { + return ! Features::hasApiFeatures(); +}, 'API support is not enabled.'); diff --git a/stubs/pest-tests/inertia/DeleteTeamTest.php b/stubs/pest-tests/inertia/DeleteTeamTest.php new file mode 100644 index 000000000..dfad3d311 --- /dev/null +++ b/stubs/pest-tests/inertia/DeleteTeamTest.php @@ -0,0 +1,29 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $user->ownedTeams()->save($team = Team::factory()->make([ + 'personal_team' => false, + ])); + + $team->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'test-role'] + ); + + $response = $this->delete('/teams/'.$team->id); + + expect($team->fresh())->toBeNull(); + expect($otherUser->fresh()->teams)->toHaveCount(0); +}); + +test('personal teams cant be deleted', function () { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + + $response = $this->delete('/teams/'.$user->currentTeam->id); + + expect($user->currentTeam->fresh())->not->toBeNull(); +}); diff --git a/stubs/pest-tests/inertia/InviteTeamMemberTest.php b/stubs/pest-tests/inertia/InviteTeamMemberTest.php new file mode 100644 index 000000000..19e81c0fa --- /dev/null +++ b/stubs/pest-tests/inertia/InviteTeamMemberTest.php @@ -0,0 +1,33 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $response = $this->post('/teams/'.$user->currentTeam->id.'/members', [ + 'email' => 'test@example.com', + 'role' => 'admin', + ]); + + Mail::assertSent(TeamInvitation::class); + + expect($user->currentTeam->fresh()->teamInvitations)->toHaveCount(1); +}); + +test('team member invitations can be cancelled', function () { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + + $invitation = $user->currentTeam->teamInvitations()->create([ + 'email' => 'test@example.com', + 'role' => 'admin', + ]); + + $response = $this->delete('/team-invitations/'.$invitation->id); + + expect($user->currentTeam->fresh()->teamInvitations)->toHaveCount(0); +}); diff --git a/stubs/pest-tests/inertia/LeaveTeamTest.php b/stubs/pest-tests/inertia/LeaveTeamTest.php new file mode 100644 index 000000000..21f09a31b --- /dev/null +++ b/stubs/pest-tests/inertia/LeaveTeamTest.php @@ -0,0 +1,27 @@ +withPersonalTeam()->create(); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $this->actingAs($otherUser); + + $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id); + + expect($user->currentTeam->fresh()->users)->toHaveCount(0); +}); + +test('team owners cant leave their own team', function () { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + + $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$user->id); + + $response->assertSessionHasErrorsIn('removeTeamMember', ['team']); + + expect($user->currentTeam->fresh())->not->toBeNull(); +}); diff --git a/stubs/pest-tests/inertia/ProfileInformationTest.php b/stubs/pest-tests/inertia/ProfileInformationTest.php new file mode 100644 index 000000000..5b6dffdb9 --- /dev/null +++ b/stubs/pest-tests/inertia/ProfileInformationTest.php @@ -0,0 +1,16 @@ +actingAs($user = User::factory()->create()); + + $response = $this->put('/user/profile-information', [ + 'name' => 'Test Name', + 'email' => 'test@example.com', + ]); + + expect($user->fresh()) + ->name->toEqual('Test Name') + ->email->toEqual('test@example.com'); +}); diff --git a/stubs/pest-tests/inertia/RemoveTeamMemberTest.php b/stubs/pest-tests/inertia/RemoveTeamMemberTest.php new file mode 100644 index 000000000..e42131ec6 --- /dev/null +++ b/stubs/pest-tests/inertia/RemoveTeamMemberTest.php @@ -0,0 +1,29 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id); + + expect($user->currentTeam->fresh()->users)->toHaveCount(0); +}); + +test('only team owner can remove team members', function () { + $user = User::factory()->withPersonalTeam()->create(); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $this->actingAs($otherUser); + + $response = $this->delete('/teams/'.$user->currentTeam->id.'/members/'.$user->id); + + $response->assertStatus(403); +}); diff --git a/stubs/pest-tests/inertia/TwoFactorAuthenticationSettingsTest.php b/stubs/pest-tests/inertia/TwoFactorAuthenticationSettingsTest.php new file mode 100644 index 000000000..fdcacf87a --- /dev/null +++ b/stubs/pest-tests/inertia/TwoFactorAuthenticationSettingsTest.php @@ -0,0 +1,44 @@ +actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $response = $this->post('/user/two-factor-authentication'); + + expect($user->fresh()->two_factor_secret)->not->toBeNull(); + expect($user->fresh()->recoveryCodes())->toHaveCount(8); +}); + +test('recovery codes can be regenerated', function () { + $this->actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $this->post('/user/two-factor-authentication'); + $this->post('/user/two-factor-recovery-codes'); + + $user = $user->fresh(); + + $this->post('/user/two-factor-recovery-codes'); + + expect($user->recoveryCodes())->toHaveCount(8); + expect(array_diff($user->recoveryCodes(), $user->fresh()->recoveryCodes()))->toHaveCount(8); +}); + +test('two factor authentication can be disabled', function () { + $this->actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $this->post('/user/two-factor-authentication'); + + $this->assertNotNull($user->fresh()->two_factor_secret); + + $this->delete('/user/two-factor-authentication'); + + expect($user->fresh()->two_factor_secret)->toBeNull(); +}); diff --git a/stubs/pest-tests/inertia/UpdatePasswordTest.php b/stubs/pest-tests/inertia/UpdatePasswordTest.php new file mode 100644 index 000000000..e440c29eb --- /dev/null +++ b/stubs/pest-tests/inertia/UpdatePasswordTest.php @@ -0,0 +1,44 @@ +actingAs($user = User::factory()->create()); + + $response = $this->put('/user/password', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]); + + expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue(); +}); + +test('current password must be correct', function () { + $this->actingAs($user = User::factory()->create()); + + $response = $this->put('/user/password', [ + 'current_password' => 'wrong-password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]); + + $response->assertSessionHasErrors(); + + expect(Hash::check('password', $user->fresh()->password))->toBeTrue(); +}); + +test('new passwords must match', function () { + $this->actingAs($user = User::factory()->create()); + + $response = $this->put('/user/password', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'wrong-password', + ]); + + $response->assertSessionHasErrors(); + + expect(Hash::check('password', $user->fresh()->password))->toBeTrue(); +}); diff --git a/stubs/pest-tests/inertia/UpdateTeamMemberRoleTest.php b/stubs/pest-tests/inertia/UpdateTeamMemberRoleTest.php new file mode 100644 index 000000000..6aa4da276 --- /dev/null +++ b/stubs/pest-tests/inertia/UpdateTeamMemberRoleTest.php @@ -0,0 +1,37 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $response = $this->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id, [ + 'role' => 'editor', + ]); + + expect($otherUser->fresh()->hasTeamRole( + $user->currentTeam->fresh(), 'editor' + ))->toBeTrue(); +}); + +test('only team owner can update team member roles', function () { + $user = User::factory()->withPersonalTeam()->create(); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $this->actingAs($otherUser); + + $response = $this->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id, [ + 'role' => 'editor', + ]); + + expect($otherUser->fresh()->hasTeamRole( + $user->currentTeam->fresh(), 'admin' + ))->toBeTrue(); +}); diff --git a/stubs/pest-tests/inertia/UpdateTeamNameTest.php b/stubs/pest-tests/inertia/UpdateTeamNameTest.php new file mode 100644 index 000000000..70ec3f631 --- /dev/null +++ b/stubs/pest-tests/inertia/UpdateTeamNameTest.php @@ -0,0 +1,14 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $response = $this->put('/teams/'.$user->currentTeam->id, [ + 'name' => 'Test Team', + ]); + + expect($user->fresh()->ownedTeams)->toHaveCount(1); + expect($user->currentTeam->fresh()->name)->toEqual('Test Team'); +}); diff --git a/stubs/pest-tests/livewire/ApiTokenPermissionsTest.php b/stubs/pest-tests/livewire/ApiTokenPermissionsTest.php new file mode 100644 index 000000000..94eb7710e --- /dev/null +++ b/stubs/pest-tests/livewire/ApiTokenPermissionsTest.php @@ -0,0 +1,38 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => ['create', 'read'], + ]); + + Livewire::test(ApiTokenManager::class) + ->set(['managingPermissionsFor' => $token]) + ->set(['updateApiTokenForm' => [ + 'permissions' => [ + 'delete', + 'missing-permission', + ], + ]]) + ->call('updateApiToken'); + + expect($user->fresh()->tokens->first()) + ->can('delete')->toBeTrue() + ->can('read')->toBeFalse() + ->can('missing-permission')->toBeFalse(); +})->skip(function () { + return ! Features::hasApiFeatures(); +}, 'API support is not enabled.'); diff --git a/stubs/pest-tests/livewire/BrowserSessionsTest.php b/stubs/pest-tests/livewire/BrowserSessionsTest.php new file mode 100644 index 000000000..dda85a072 --- /dev/null +++ b/stubs/pest-tests/livewire/BrowserSessionsTest.php @@ -0,0 +1,13 @@ +actingAs($user = User::factory()->create()); + + Livewire::test(LogoutOtherBrowserSessionsForm::class) + ->set('password', 'password') + ->call('logoutOtherBrowserSessions'); +}); diff --git a/stubs/pest-tests/livewire/CreateApiTokenTest.php b/stubs/pest-tests/livewire/CreateApiTokenTest.php new file mode 100644 index 000000000..8031240c0 --- /dev/null +++ b/stubs/pest-tests/livewire/CreateApiTokenTest.php @@ -0,0 +1,32 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + Livewire::test(ApiTokenManager::class) + ->set(['createApiTokenForm' => [ + 'name' => 'Test Token', + 'permissions' => [ + 'read', + 'update', + ], + ]]) + ->call('createApiToken'); + + expect($user->fresh()->tokens)->toHaveCount(1); + expect($user->fresh()->tokens->first()) + ->name->toEqual('Test Token') + ->can('read')->toBeTrue() + ->can('delete')->toBeFalse(); +})->skip(function () { + return ! Features::hasApiFeatures(); +}, 'API support is not enabled.'); diff --git a/stubs/pest-tests/livewire/CreateTeamTest.php b/stubs/pest-tests/livewire/CreateTeamTest.php new file mode 100644 index 000000000..433b7aebd --- /dev/null +++ b/stubs/pest-tests/livewire/CreateTeamTest.php @@ -0,0 +1,16 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + Livewire::test(CreateTeamForm::class) + ->set(['state' => ['name' => 'Test Team']]) + ->call('createTeam'); + + expect($user->fresh()->ownedTeams)->toHaveCount(2); + expect($user->fresh()->ownedTeams()->latest('id')->first()->name)->toEqual('Test Team'); +}); diff --git a/stubs/pest-tests/livewire/DeleteAccountTest.php b/stubs/pest-tests/livewire/DeleteAccountTest.php new file mode 100644 index 000000000..d5c9a311a --- /dev/null +++ b/stubs/pest-tests/livewire/DeleteAccountTest.php @@ -0,0 +1,31 @@ +actingAs($user = User::factory()->create()); + + $component = Livewire::test(DeleteUserForm::class) + ->set('password', 'password') + ->call('deleteUser'); + + expect($user->fresh())->toBeNull(); +})->skip(function () { + return ! Features::hasAccountDeletionFeatures(); +}, 'Account deletion is not enabled.'); + +test('correct password must be provided before account can be deleted', function () { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(DeleteUserForm::class) + ->set('password', 'wrong-password') + ->call('deleteUser') + ->assertHasErrors(['password']); + + expect($user->fresh())->not->toBeNull(); +})->skip(function () { + return ! Features::hasAccountDeletionFeatures(); +}, 'Account deletion is not enabled.'); diff --git a/stubs/pest-tests/livewire/DeleteApiTokenTest.php b/stubs/pest-tests/livewire/DeleteApiTokenTest.php new file mode 100644 index 000000000..1b512fcda --- /dev/null +++ b/stubs/pest-tests/livewire/DeleteApiTokenTest.php @@ -0,0 +1,29 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + } else { + $this->actingAs($user = User::factory()->create()); + } + + $token = $user->tokens()->create([ + 'name' => 'Test Token', + 'token' => Str::random(40), + 'abilities' => ['create', 'read'], + ]); + + Livewire::test(ApiTokenManager::class) + ->set(['apiTokenIdBeingDeleted' => $token->id]) + ->call('deleteApiToken'); + + expect($user->fresh()->tokens)->toHaveCount(0); +})->skip(function () { + return ! Features::hasApiFeatures(); +}, 'API support is not enabled.'); diff --git a/stubs/pest-tests/livewire/DeleteTeamTest.php b/stubs/pest-tests/livewire/DeleteTeamTest.php new file mode 100644 index 000000000..ffe37d2cf --- /dev/null +++ b/stubs/pest-tests/livewire/DeleteTeamTest.php @@ -0,0 +1,34 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $user->ownedTeams()->save($team = Team::factory()->make([ + 'personal_team' => false, + ])); + + $team->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'test-role'] + ); + + $component = Livewire::test(DeleteTeamForm::class, ['team' => $team->fresh()]) + ->call('deleteTeam'); + + expect($team->fresh())->toBeNull(); + expect($otherUser->fresh()->teams)->toHaveCount(0); +}); + +test('personal teams cant be deleted', function () { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + + $component = Livewire::test(DeleteTeamForm::class, ['team' => $user->currentTeam]) + ->call('deleteTeam') + ->assertHasErrors(['team']); + + expect($user->currentTeam->fresh())->not->toBeNull(); +}); diff --git a/stubs/pest-tests/livewire/InviteTeamMemberTest.php b/stubs/pest-tests/livewire/InviteTeamMemberTest.php new file mode 100644 index 000000000..e611e9c44 --- /dev/null +++ b/stubs/pest-tests/livewire/InviteTeamMemberTest.php @@ -0,0 +1,41 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->set('addTeamMemberForm', [ + 'email' => 'test@example.com', + 'role' => 'admin', + ])->call('addTeamMember'); + + Mail::assertSent(TeamInvitation::class); + + expect($user->currentTeam->fresh()->teamInvitations)->toHaveCount(1); +}); + +test('team member invitations can be cancelled', function () { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + + // Add the team member... + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->set('addTeamMemberForm', [ + 'email' => 'test@example.com', + 'role' => 'admin', + ])->call('addTeamMember'); + + $invitationId = $user->currentTeam->fresh()->teamInvitations->first()->id; + + // Cancel the team invitation... + $component->call('cancelTeamInvitation', $invitationId); + + expect($user->currentTeam->fresh()->teamInvitations)->toHaveCount(0); +}); diff --git a/stubs/pest-tests/livewire/LeaveTeamTest.php b/stubs/pest-tests/livewire/LeaveTeamTest.php new file mode 100644 index 000000000..9696a5cb5 --- /dev/null +++ b/stubs/pest-tests/livewire/LeaveTeamTest.php @@ -0,0 +1,30 @@ +withPersonalTeam()->create(); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $this->actingAs($otherUser); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->call('leaveTeam'); + + expect($user->currentTeam->fresh()->users)->toHaveCount(0); +}); + +test('team owners cant leave their own team', function () { + $this->actingAs($user = User::factory()->withPersonalTeam()->create()); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->call('leaveTeam') + ->assertHasErrors(['team']); + + expect($user->currentTeam->fresh())->not->toBeNull(); +}); diff --git a/stubs/pest-tests/livewire/ProfileInformationTest.php b/stubs/pest-tests/livewire/ProfileInformationTest.php new file mode 100644 index 000000000..bfc673bb5 --- /dev/null +++ b/stubs/pest-tests/livewire/ProfileInformationTest.php @@ -0,0 +1,26 @@ +actingAs($user = User::factory()->create()); + + $component = Livewire::test(UpdateProfileInformationForm::class); + + expect($component->state['name'])->toEqual($user->name); + expect($component->state['email'])->toEqual($user->email); +}); + +test('profile information can be updated', function () { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(UpdateProfileInformationForm::class) + ->set('state', ['name' => 'Test Name', 'email' => 'test@example.com']) + ->call('updateProfileInformation'); + + expect($user->fresh()) + ->name->toEqual('Test Name') + ->email->toEqual('test@example.com'); +}); diff --git a/stubs/pest-tests/livewire/RemoveTeamMemberTest.php b/stubs/pest-tests/livewire/RemoveTeamMemberTest.php new file mode 100644 index 000000000..c823dcabe --- /dev/null +++ b/stubs/pest-tests/livewire/RemoveTeamMemberTest.php @@ -0,0 +1,34 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->set('teamMemberIdBeingRemoved', $otherUser->id) + ->call('removeTeamMember'); + + expect($user->currentTeam->fresh()->users)->toHaveCount(0); +}); + +test('only team owner can remove team members', function () { + $user = User::factory()->withPersonalTeam()->create(); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $this->actingAs($otherUser); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->set('teamMemberIdBeingRemoved', $user->id) + ->call('removeTeamMember') + ->assertStatus(403); +}); diff --git a/stubs/pest-tests/livewire/TwoFactorAuthenticationSettingsTest.php b/stubs/pest-tests/livewire/TwoFactorAuthenticationSettingsTest.php new file mode 100644 index 000000000..c5309272e --- /dev/null +++ b/stubs/pest-tests/livewire/TwoFactorAuthenticationSettingsTest.php @@ -0,0 +1,51 @@ +actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + Livewire::test(TwoFactorAuthenticationForm::class) + ->call('enableTwoFactorAuthentication'); + + $user = $user->fresh(); + + expect($user->two_factor_secret)->not->toBeNull(); + expect($user->recoveryCodes())->toHaveCount(8); +}); + +test('recovery codes can be regenerated', function () { + $this->actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $component = Livewire::test(TwoFactorAuthenticationForm::class) + ->call('enableTwoFactorAuthentication') + ->call('regenerateRecoveryCodes'); + + $user = $user->fresh(); + + $component->call('regenerateRecoveryCodes'); + + expect($user->recoveryCodes())->toHaveCount(8); + expect(array_diff($user->recoveryCodes(), $user->fresh()->recoveryCodes()))->toHaveCount(8); +}); + +test('two factor authentication can be disabled', function () { + $this->actingAs($user = User::factory()->create()); + + $this->withSession(['auth.password_confirmed_at' => time()]); + + $component = Livewire::test(TwoFactorAuthenticationForm::class) + ->call('enableTwoFactorAuthentication'); + + $this->assertNotNull($user->fresh()->two_factor_secret); + + $component->call('disableTwoFactorAuthentication'); + + expect($user->fresh()->two_factor_secret)->toBeNull(); +}); diff --git a/stubs/pest-tests/livewire/UpdatePasswordTest.php b/stubs/pest-tests/livewire/UpdatePasswordTest.php new file mode 100644 index 000000000..dfea437ff --- /dev/null +++ b/stubs/pest-tests/livewire/UpdatePasswordTest.php @@ -0,0 +1,50 @@ +actingAs($user = User::factory()->create()); + + Livewire::test(UpdatePasswordForm::class) + ->set('state', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]) + ->call('updatePassword'); + + expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue(); +}); + +test('current password must be correct', function () { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(UpdatePasswordForm::class) + ->set('state', [ + 'current_password' => 'wrong-password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]) + ->call('updatePassword') + ->assertHasErrors(['current_password']); + + expect(Hash::check('password', $user->fresh()->password))->toBeTrue(); +}); + +test('new passwords must match', function () { + $this->actingAs($user = User::factory()->create()); + + Livewire::test(UpdatePasswordForm::class) + ->set('state', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'wrong-password', + ]) + ->call('updatePassword') + ->assertHasErrors(['password']); + + expect(Hash::check('password', $user->fresh()->password))->toBeTrue(); +}); diff --git a/stubs/pest-tests/livewire/UpdateTeamMemberRoleTest.php b/stubs/pest-tests/livewire/UpdateTeamMemberRoleTest.php new file mode 100644 index 000000000..24a5f177c --- /dev/null +++ b/stubs/pest-tests/livewire/UpdateTeamMemberRoleTest.php @@ -0,0 +1,42 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->set('managingRoleFor', $otherUser) + ->set('currentRole', 'editor') + ->call('updateRole'); + + expect($otherUser->fresh()->hasTeamRole( + $user->currentTeam->fresh(), 'editor' + ))->toBeTrue(); +}); + +test('only team owner can update team member roles', function () { + $user = User::factory()->withPersonalTeam()->create(); + + $user->currentTeam->users()->attach( + $otherUser = User::factory()->create(), ['role' => 'admin'] + ); + + $this->actingAs($otherUser); + + $component = Livewire::test(TeamMemberManager::class, ['team' => $user->currentTeam]) + ->set('managingRoleFor', $otherUser) + ->set('currentRole', 'editor') + ->call('updateRole') + ->assertStatus(403); + + expect($otherUser->fresh()->hasTeamRole( + $user->currentTeam->fresh(), 'admin' + ))->toBeTrue(); +}); diff --git a/stubs/pest-tests/livewire/UpdateTeamNameTest.php b/stubs/pest-tests/livewire/UpdateTeamNameTest.php new file mode 100644 index 000000000..c299bdbac --- /dev/null +++ b/stubs/pest-tests/livewire/UpdateTeamNameTest.php @@ -0,0 +1,16 @@ +actingAs($user = User::factory()->withPersonalTeam()->create()); + + Livewire::test(UpdateTeamNameForm::class, ['team' => $user->currentTeam]) + ->set(['state' => ['name' => 'Test Team']]) + ->call('updateTeamName'); + + expect($user->fresh()->ownedTeams)->toHaveCount(1); + expect($user->currentTeam->fresh()->name)->toEqual('Test Team'); +});