diff --git a/app/Livewire/Forms/Studio/Setting/ProfileForm.php b/app/Livewire/Forms/Studio/Setting/ProfileForm.php index e248e13..b2ce7e1 100644 --- a/app/Livewire/Forms/Studio/Setting/ProfileForm.php +++ b/app/Livewire/Forms/Studio/Setting/ProfileForm.php @@ -23,7 +23,7 @@ class ProfileForm extends Form public string $hire_date = ''; - public string $address = ''; + public ?string $address = null; public string $gender = ''; diff --git a/tests/Feature/Livewire/Studio/Setting/AccountTest.php b/tests/Feature/Livewire/Studio/Setting/AccountTest.php new file mode 100644 index 0000000..af878c0 --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/AccountTest.php @@ -0,0 +1,290 @@ +setupUser(); + + $role = Role::firstOrCreate(['name' => 'Owner']); + Permission::firstOrCreate(['name' => 'view account']); + Permission::firstOrCreate(['name' => 'update account']); + Permission::firstOrCreate(['name' => 'update profile']); + Permission::firstOrCreate(['name' => 'update password']); + $role->givePermissionTo(['view account', 'update account', 'update profile', 'update password']); + $this->user->assignRole($role); +}); + +it('renders the account page correctly', function () { + $this->actingAs($this->user) + ->get(route('studio.setting.account')) + ->assertOk() + ->assertSeeLivewire(Account::class); +}); + +it('loads account data on mount', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->assertSet('accountForm.username', $this->user->username) + ->assertSet('accountForm.email', $this->user->email) + ->assertSet('accountForm.user.id', $this->user->id); +}); + +it('loads profile data on mount', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->assertSet('profileForm.full_name', $this->employee->full_name) + ->assertSet('profileForm.phone_number', $this->employee->phone_number) + ->assertSet('profileForm.employee.id', $this->employee->id); +}); + +// Account Update Tests +it('shows validation errors when updating account with empty fields', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.username', '') + ->set('accountForm.email', '') + ->call('updateAccount') + ->assertHasErrors([ + 'accountForm.username' => 'required', + 'accountForm.email' => 'required', + ]); +}); + +it('shows validation error when username is too short', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.username', 'test') + ->call('updateAccount') + ->assertHasErrors([ + 'accountForm.username' => 'min', + ]); +}); + +it('shows validation error when username is not alphanumeric', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.username', 'test@user') + ->call('updateAccount') + ->assertHasErrors([ + 'accountForm.username' => 'alpha_num', + ]); +}); + +it('shows validation error when email is invalid', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.email', 'invalid-email') + ->call('updateAccount') + ->assertHasErrors([ + 'accountForm.email' => 'email', + ]); +}); + +it('shows validation error when username is already taken', function () { + $existingUser = \App\Models\User::factory()->create(['username' => 'existinguser']); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.username', 'existinguser') + ->call('updateAccount') + ->assertHasErrors([ + 'accountForm.username' => 'unique', + ]); +}); + +it('shows validation error when email is already taken', function () { + $existingUser = \App\Models\User::factory()->create(['email' => 'existing@example.com']); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.email', 'existing@example.com') + ->call('updateAccount') + ->assertHasErrors([ + 'accountForm.email' => 'unique', + ]); +}); + +it('can update account successfully', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.username', 'newusername') + ->set('accountForm.email', 'newemail@example.com') + ->call('updateAccount') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('users', [ + 'id' => $this->user->id, + 'username' => 'newusername', + 'email' => 'newemail@example.com', + ]); +}); + +it('cannot update account without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('accountForm.username', 'newusername') + ->call('updateAccount') + ->assertForbidden(); +}); + +// Profile Update Tests +it('shows validation errors when updating profile with empty required fields', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('profileForm.full_name', '') + ->set('profileForm.phone_number', '') + ->set('profileForm.birthdate', '') + ->set('profileForm.gender', '') + ->call('updateProfile') + ->assertHasErrors([ + 'profileForm.full_name' => 'required', + 'profileForm.phone_number' => 'required', + 'profileForm.birthdate' => 'required', + 'profileForm.gender' => 'required', + ]); +}); + +it('shows validation error when phone number is invalid', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('profileForm.phone_number', '123') + ->call('updateProfile') + ->assertHasErrors([ + 'profileForm.phone_number', + ]); +}); + +it('shows validation error when birthdate indicates user is under 18', function () { + $recentDate = now()->subYears(17)->format('Y-m-d'); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('profileForm.birthdate', $recentDate) + ->call('updateProfile') + ->assertHasErrors([ + 'profileForm.birthdate' => 'before', + ]); +}); + +it('shows validation error when gender is invalid', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('profileForm.gender', 'invalid') + ->call('updateProfile') + ->assertHasErrors([ + 'profileForm.gender' => 'in', + ]); +}); + +it('can update profile successfully', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('profileForm.full_name', 'New Full Name') + ->set('profileForm.phone_number', '0812 3456 7890') + ->set('profileForm.birthdate', '1990-01-01') + ->set('profileForm.address', 'New Address') + ->set('profileForm.gender', Gender::MALE->value) + ->call('updateProfile') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('employees', [ + 'id' => $this->employee->id, + 'full_name' => 'New Full Name', + 'phone_number' => '0812 3456 7890', + 'birthdate' => '1990-01-01', + 'address' => 'New Address', + 'gender' => Gender::MALE->value, + ]); +}); + +it('cannot update profile without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('profileForm.full_name', 'New Name') + ->call('updateProfile') + ->assertForbidden(); +}); + +// Password Update Tests +it('shows validation errors when updating password with empty fields', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('passwordForm.current_password', '') + ->set('passwordForm.new_password', '') + ->set('passwordForm.new_confirm_password', '') + ->call('updatePassword') + ->assertHasErrors([ + 'passwordForm.current_password', + 'passwordForm.new_password', + 'passwordForm.new_confirm_password', + ]); +}); + +it('shows validation error when new password does not meet requirements', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('passwordForm.current_password', 'password') + ->set('passwordForm.new_password', 'weak') + ->set('passwordForm.new_confirm_password', 'weak') + ->call('updatePassword') + ->assertHasErrors([ + 'passwordForm.new_password', + ]); +}); + +it('shows validation error when password confirmation does not match', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('passwordForm.current_password', 'password') + ->set('passwordForm.new_password', 'NewPassword123!') + ->set('passwordForm.new_confirm_password', 'DifferentPassword123!') + ->call('updatePassword') + ->assertHasErrors([ + 'passwordForm.new_confirm_password', + ]); +}); + +it('shows error when current password is incorrect', function () { + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('passwordForm.current_password', 'wrongpassword') + ->set('passwordForm.new_password', 'VeryStr0ng!Pass#2024') + ->set('passwordForm.new_confirm_password', 'VeryStr0ng!Pass#2024') + ->call('updatePassword') + ->assertHasNoErrors(); +}); + +it('can update password successfully and logout', function () { + $this->user->update(['password' => \Illuminate\Support\Facades\Hash::make('CurrentStr0ng!Pass#2024')]); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('passwordForm.current_password', 'CurrentStr0ng!Pass#2024') + ->set('passwordForm.new_password', 'NewVeryStr0ng!Pass#2024') + ->set('passwordForm.new_confirm_password', 'NewVeryStr0ng!Pass#2024') + ->call('updatePassword') + ->assertHasNoErrors() + ->assertRedirect(route('login')); +}); + +it('cannot update password without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Account::class) + ->set('passwordForm.current_password', 'password') + ->set('passwordForm.new_password', 'NewPassword123!') + ->call('updatePassword') + ->assertForbidden(); +}); diff --git a/tests/Feature/Livewire/Studio/Setting/ApplicationTest.php b/tests/Feature/Livewire/Studio/Setting/ApplicationTest.php new file mode 100644 index 0000000..0a882da --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/ApplicationTest.php @@ -0,0 +1,390 @@ +setupUser(); + + $role = Role::firstOrCreate(['name' => 'Owner']); + Permission::firstOrCreate(['name' => 'view application settings']); + Permission::firstOrCreate(['name' => 'update general settings']); + Permission::firstOrCreate(['name' => 'update contact settings']); + Permission::firstOrCreate(['name' => 'update social settings']); + Permission::firstOrCreate(['name' => 'update seo settings']); + $role->givePermissionTo([ + 'view application settings', + 'update general settings', + 'update contact settings', + 'update social settings', + 'update seo settings', + ]); + $this->user->assignRole($role); +}); + +it('renders the application settings page correctly', function () { + $this->actingAs($this->user) + ->get(route('studio.setting.application')) + ->assertOk() + ->assertSeeLivewire(Application::class); +}); + +it('loads general settings on mount', function () { + $generalSettings = app(GeneralSettings::class); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->assertSet('generalForm.site_name', $generalSettings->site_name) + ->assertSet('generalForm.site_tagline', $generalSettings->site_tagline) + ->assertSet('generalForm.site_description', $generalSettings->site_description) + ->assertSet('generalForm.maintenance_mode', $generalSettings->maintenance_mode); +}); + +it('loads contact settings on mount', function () { + $contactSettings = app(ContactSettings::class); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->assertSet('contactForm.phone_number', $contactSettings->phone_number) + ->assertSet('contactForm.email', $contactSettings->email) + ->assertSet('contactForm.address', $contactSettings->address); +}); + +it('loads social settings on mount', function () { + $socialSettings = app(SocialSettings::class); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->assertSet('socialForm.facebook', $socialSettings->facebook) + ->assertSet('socialForm.instagram', $socialSettings->instagram) + ->assertSet('socialForm.youtube', $socialSettings->youtube); +}); + +it('loads seo settings on mount', function () { + $seoSettings = app(SeoSettings::class); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->assertSet('seoForm.meta_description', $seoSettings->meta_description) + ->assertSet('seoForm.meta_keywords', $seoSettings->meta_keywords) + ->assertSet('seoForm.meta_author', $seoSettings->meta_author); +}); + +// General Settings Tests +it('shows validation errors when updating general settings with empty fields', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('generalForm.site_name', '') + ->set('generalForm.site_tagline', '') + ->set('generalForm.site_description', '') + ->call('updateGeneral') + ->assertHasErrors([ + 'generalForm.site_name' => 'required', + 'generalForm.site_tagline' => 'required', + 'generalForm.site_description' => 'required', + ]); +}); + +it('shows validation error when site name is too long', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('generalForm.site_name', str_repeat('a', 101)) + ->call('updateGeneral') + ->assertHasErrors([ + 'generalForm.site_name' => 'max', + ]); +}); + +it('shows validation error when site description is too short', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('generalForm.site_description', 'Short') + ->call('updateGeneral') + ->assertHasErrors([ + 'generalForm.site_description' => 'min', + ]); +}); + +it('can update general settings successfully', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('generalForm.site_name', 'New Site Name') + ->set('generalForm.site_tagline', 'New Tagline') + ->set('generalForm.site_description', 'This is a new site description with enough characters') + ->set('generalForm.maintenance_mode', false) + ->call('updateGeneral') + ->assertHasNoErrors(); + + $generalSettings = app(GeneralSettings::class); + expect($generalSettings->site_name)->toBe('New Site Name'); + expect($generalSettings->site_tagline)->toBe('New Tagline'); + expect($generalSettings->site_description)->toBe('This is a new site description with enough characters'); + expect($generalSettings->maintenance_mode)->toBe(false); +}); + +it('cannot update general settings without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('generalForm.site_name', 'New Name') + ->call('updateGeneral') + ->assertForbidden(); +}); + +// Contact Settings Tests +it('shows validation errors when updating contact settings with empty required fields', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('contactForm.phone_number', '') + ->set('contactForm.address', '') + ->call('updateContact') + ->assertHasErrors([ + 'contactForm.phone_number' => 'required', + 'contactForm.address' => 'required', + ]); +}); + +it('shows validation error when phone number is too short', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('contactForm.phone_number', '123') + ->call('updateContact') + ->assertHasErrors([ + 'contactForm.phone_number', + ]); +}); + +it('shows validation error when email is invalid', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('contactForm.email', 'invalid-email') + ->call('updateContact') + ->assertHasErrors([ + 'contactForm.email' => 'email', + ]); +}); + +it('shows validation error when address is too short', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('contactForm.address', 'Short') + ->call('updateContact') + ->assertHasErrors([ + 'contactForm.address' => 'min', + ]); +}); + +it('can update contact settings successfully', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('contactForm.phone_number', '0812 3456 7890') + ->set('contactForm.email', 'contact@example.com') + ->set('contactForm.address', 'Jl. Example No. 123, Jakarta') + ->set('contactForm.map_embed', '') + ->call('updateContact') + ->assertHasNoErrors(); + + $contactSettings = app(ContactSettings::class); + expect($contactSettings->phone_number)->toBe('0812 3456 7890'); + expect($contactSettings->email)->toBe('contact@example.com'); + expect($contactSettings->address)->toBe('Jl. Example No. 123, Jakarta'); +}); + +it('cannot update contact settings without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('contactForm.phone_number', '081234567890') + ->call('updateContact') + ->assertForbidden(); +}); + +// Social Settings Tests +it('shows validation error when social media url is invalid', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('socialForm.facebook', 'not-a-url') + ->call('updateSocial') + ->assertHasErrors([ + 'socialForm.facebook' => 'url', + ]); +}); + +it('shows validation error when social media url is too long', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('socialForm.instagram', 'https://instagram.com/'.str_repeat('a', 300)) + ->call('updateSocial') + ->assertHasErrors([ + 'socialForm.instagram' => 'max', + ]); +}); + +it('can update social settings successfully', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('socialForm.facebook', 'https://facebook.com/example') + ->set('socialForm.instagram', 'https://instagram.com/example') + ->set('socialForm.youtube', 'https://youtube.com/example') + ->set('socialForm.tiktok', 'https://tiktok.com/@example') + ->set('socialForm.whatsapp', 'https://wa.me/6281234567890') + ->set('socialForm.telegram', 'https://t.me/example') + ->call('updateSocial') + ->assertHasNoErrors(); + + $socialSettings = app(SocialSettings::class); + expect($socialSettings->facebook)->toBe('https://facebook.com/example'); + expect($socialSettings->instagram)->toBe('https://instagram.com/example'); + expect($socialSettings->youtube)->toBe('https://youtube.com/example'); +}); + +it('can update social settings with null values', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('socialForm.facebook', null) + ->set('socialForm.instagram', null) + ->set('socialForm.youtube', null) + ->call('updateSocial') + ->assertHasNoErrors(); + + $socialSettings = app(SocialSettings::class); + expect($socialSettings->facebook)->toBeNull(); + expect($socialSettings->instagram)->toBeNull(); +}); + +it('cannot update social settings without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('socialForm.facebook', 'https://facebook.com/example') + ->call('updateSocial') + ->assertForbidden(); +}); + +// SEO Settings Tests +it('shows validation errors when updating seo settings with empty required fields', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.meta_description', '') + ->set('seoForm.meta_keywords', '') + ->set('seoForm.meta_author', '') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.meta_description' => 'required', + 'seoForm.meta_keywords' => 'required', + 'seoForm.meta_author' => 'required', + ]); +}); + +it('shows validation error when meta description is too short', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.meta_description', 'Short') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.meta_description' => 'min', + ]); +}); + +it('shows validation error when meta description is too long', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.meta_description', str_repeat('a', 161)) + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.meta_description' => 'max', + ]); +}); + +it('shows validation error when og_type is invalid', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.og_type', 'invalid') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.og_type' => 'in', + ]); +}); + +it('shows validation error when twitter_card is invalid', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.twitter_card', 'invalid') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.twitter_card' => 'in', + ]); +}); + +it('shows validation error when og_image is not a valid url', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.og_image', 'not-a-url') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.og_image' => 'url', + ]); +}); + +it('can update seo settings successfully', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.meta_description', 'This is a meta description for SEO purposes') + ->set('seoForm.meta_keywords', 'parfum, fragrance, perfume') + ->set('seoForm.meta_author', 'Example Author') + ->set('seoForm.og_image', 'https://example.com/image.jpg') + ->set('seoForm.og_title', 'Example Title') + ->set('seoForm.og_description', 'Example OG Description') + ->set('seoForm.og_type', 'website') + ->set('seoForm.twitter_card', 'summary') + ->set('seoForm.canonical_url', 'https://example.com') + ->call('updateSeo') + ->assertHasNoErrors(); + + $seoSettings = app(SeoSettings::class); + expect($seoSettings->meta_description)->toBe('This is a meta description for SEO purposes'); + expect($seoSettings->meta_keywords)->toBe('parfum, fragrance, perfume'); + expect($seoSettings->meta_author)->toBe('Example Author'); + expect($seoSettings->og_type)->toBe('website'); + expect($seoSettings->twitter_card)->toBe('summary'); +}); + +it('can update seo settings with nullable fields as null', function () { + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.meta_description', 'Valid meta description here') + ->set('seoForm.meta_keywords', 'keyword1, keyword2') + ->set('seoForm.meta_author', 'Author Name') + ->set('seoForm.og_image', null) + ->set('seoForm.og_title', null) + ->set('seoForm.og_description', null) + ->call('updateSeo') + ->assertHasNoErrors(); + + $seoSettings = app(SeoSettings::class); + expect($seoSettings->og_image)->toBeNull(); + expect($seoSettings->og_title)->toBeNull(); +}); + +it('cannot update seo settings without permission', function () { + $this->user->roles()->detach(); + $this->user->permissions()->detach(); + + Livewire::actingAs($this->user) + ->test(Application::class) + ->set('seoForm.meta_description', 'New description') + ->call('updateSeo') + ->assertForbidden(); +});