From 224ea30a2a99c785567bad7ebfa79fcd573a601e Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 10 Dec 2025 11:38:44 +0700 Subject: [PATCH] feat(setting): implementasi maintenance mode - membuat config baru untuk secret key - implementasi test - menyesuaikan validasi - kasih return type --- .../Forms/Studio/Setting/ContactForm.php | 16 +- .../Forms/Studio/Setting/GeneralForm.php | 20 +- app/Livewire/Forms/Studio/Setting/SeoForm.php | 31 +- .../Forms/Studio/Setting/SocialForm.php | 14 +- app/Livewire/Studio/Setting/Application.php | 26 +- config/app.php | 1 + .../Studio/Setting/ApplicationTest.php | 301 +++++++++++++++ .../Studio/Setting/ContactFormTest.php | 201 ++++++++++ .../Studio/Setting/GeneralFormTest.php | 143 +++++++ .../Livewire/Studio/Setting/SeoFormTest.php | 350 ++++++++++++++++++ .../Studio/Setting/SocialFormTest.php | 117 ++++++ 11 files changed, 1186 insertions(+), 34 deletions(-) create mode 100644 tests/Feature/Livewire/Studio/Setting/ApplicationTest.php create mode 100644 tests/Feature/Livewire/Studio/Setting/ContactFormTest.php create mode 100644 tests/Feature/Livewire/Studio/Setting/GeneralFormTest.php create mode 100644 tests/Feature/Livewire/Studio/Setting/SeoFormTest.php create mode 100644 tests/Feature/Livewire/Studio/Setting/SocialFormTest.php diff --git a/app/Livewire/Forms/Studio/Setting/ContactForm.php b/app/Livewire/Forms/Studio/Setting/ContactForm.php index ae08734..5bc6918 100644 --- a/app/Livewire/Forms/Studio/Setting/ContactForm.php +++ b/app/Livewire/Forms/Studio/Setting/ContactForm.php @@ -4,7 +4,6 @@ use App\Rules\PhoneNumber; use App\Settings\ContactSettings; -use Illuminate\Validation\Rule; use Livewire\Form; class ContactForm extends Form @@ -20,10 +19,10 @@ class ContactForm extends Form public function rules(): array { return [ - 'phone_number' => ['required', 'string', new PhoneNumber], - 'email' => ['nullable', 'email', 'max:100', Rule::unique('users', 'email')], - 'address' => ['required', 'string', 'max:255'], - 'map_embed' => ['nullable', 'string'], + 'phone_number' => ['required', 'string', 'min:10', 'max:20', new PhoneNumber], + 'email' => ['nullable', 'email', 'max:100'], + 'address' => ['required', 'string', 'min:10', 'max:500'], + 'map_embed' => ['nullable', 'string', 'max:2000'], ]; } @@ -31,16 +30,17 @@ public function validationAttributes(): array { return [ 'phone_number' => 'nomor telepon', + 'email' => 'email', 'address' => 'alamat', - 'map_embed' => 'map embed', + 'map_embed' => 'embed peta', ]; } public function setSettings(ContactSettings $settings): void { - $this->address = $settings->address; $this->phone_number = $settings->phone_number; $this->email = $settings->email; + $this->address = $settings->address; $this->map_embed = $settings->map_embed; } @@ -48,9 +48,9 @@ public function update(ContactSettings $settings): void { $this->validate(); - $settings->address = $this->address; $settings->phone_number = $this->phone_number; $settings->email = $this->email; + $settings->address = $this->address; $settings->map_embed = $this->map_embed; $settings->save(); diff --git a/app/Livewire/Forms/Studio/Setting/GeneralForm.php b/app/Livewire/Forms/Studio/Setting/GeneralForm.php index 7b2dd10..7a32944 100644 --- a/app/Livewire/Forms/Studio/Setting/GeneralForm.php +++ b/app/Livewire/Forms/Studio/Setting/GeneralForm.php @@ -18,13 +18,23 @@ class GeneralForm extends Form public function rules(): array { return [ - 'site_name' => ['required', 'string', 'max:100'], - 'site_tagline' => ['required', 'string', 'max:150'], - 'site_description' => ['required', 'string'], + 'site_name' => ['required', 'string', 'min:1', 'max:100'], + 'site_tagline' => ['required', 'string', 'min:1', 'max:150'], + 'site_description' => ['required', 'string', 'min:10', 'max:500'], 'maintenance_mode' => ['required', 'boolean'], ]; } + public function validationAttributes(): array + { + return [ + 'site_name' => 'nama situs', + 'site_tagline' => 'tagline situs', + 'site_description' => 'deskripsi situs', + 'maintenance_mode' => 'mode maintenance', + ]; + } + public function setSettings(GeneralSettings $settings): void { $this->site_name = $settings->site_name; @@ -33,7 +43,7 @@ public function setSettings(GeneralSettings $settings): void $this->maintenance_mode = $settings->maintenance_mode; } - public function update(GeneralSettings $settings): void + public function update(GeneralSettings $settings): bool { $this->validate(); @@ -43,5 +53,7 @@ public function update(GeneralSettings $settings): void $settings->maintenance_mode = $this->maintenance_mode; $settings->save(); + + return $settings->maintenance_mode; } } diff --git a/app/Livewire/Forms/Studio/Setting/SeoForm.php b/app/Livewire/Forms/Studio/Setting/SeoForm.php index f03ae5f..439419a 100644 --- a/app/Livewire/Forms/Studio/Setting/SeoForm.php +++ b/app/Livewire/Forms/Studio/Setting/SeoForm.php @@ -28,15 +28,30 @@ class SeoForm extends Form public function rules(): array { return [ - 'meta_description' => ['required', 'string'], - 'meta_keywords' => ['required', 'string'], - 'meta_author' => ['required', 'string', 'max:150'], - 'og_image' => ['nullable', 'string', 'max:255'], + 'meta_description' => ['required', 'string', 'min:10', 'max:160'], + 'meta_keywords' => ['required', 'string', 'min:3', 'max:255'], + 'meta_author' => ['required', 'string', 'min:1', 'max:150'], + 'og_image' => ['nullable', 'string', 'max:500', 'url'], 'og_title' => ['nullable', 'string', 'max:255'], - 'og_description' => ['nullable', 'string'], - 'og_type' => ['nullable', 'string', 'max:50'], - 'twitter_card' => ['nullable', 'string', 'max:50'], - 'canonical_url' => ['nullable', 'string', 'max:255'], + 'og_description' => ['nullable', 'string', 'max:300'], + 'og_type' => ['nullable', 'string', 'max:50', 'in:website,article,product'], + 'twitter_card' => ['nullable', 'string', 'max:50', 'in:summary,summary_large_image'], + 'canonical_url' => ['nullable', 'string', 'max:500', 'url'], + ]; + } + + public function validationAttributes(): array + { + return [ + 'meta_description' => 'meta deskripsi', + 'meta_keywords' => 'meta kata kunci', + 'meta_author' => 'meta penulis', + 'og_image' => 'gambar Open Graph', + 'og_title' => 'judul Open Graph', + 'og_description' => 'deskripsi Open Graph', + 'og_type' => 'tipe Open Graph', + 'twitter_card' => 'kartu Twitter', + 'canonical_url' => 'URL kanonis', ]; } diff --git a/app/Livewire/Forms/Studio/Setting/SocialForm.php b/app/Livewire/Forms/Studio/Setting/SocialForm.php index 510cf4f..e908105 100644 --- a/app/Livewire/Forms/Studio/Setting/SocialForm.php +++ b/app/Livewire/Forms/Studio/Setting/SocialForm.php @@ -19,15 +19,15 @@ class SocialForm extends Form public ?string $telegram = null; - protected function rules(): array + public function rules(): array { return [ - 'facebook' => ['nullable', 'url'], - 'instagram' => ['nullable', 'url'], - 'youtube' => ['nullable', 'url'], - 'tiktok' => ['nullable', 'url'], - 'whatsapp' => ['required', 'url'], - 'telegram' => ['nullable', 'url'], + 'facebook' => ['nullable', 'url', 'max:255'], + 'instagram' => ['nullable', 'url', 'max:255'], + 'youtube' => ['nullable', 'url', 'max:255'], + 'tiktok' => ['nullable', 'url', 'max:255'], + 'whatsapp' => ['nullable', 'url', 'max:255'], + 'telegram' => ['nullable', 'url', 'max:255'], ]; } diff --git a/app/Livewire/Studio/Setting/Application.php b/app/Livewire/Studio/Setting/Application.php index 089a06d..b70231a 100644 --- a/app/Livewire/Studio/Setting/Application.php +++ b/app/Livewire/Studio/Setting/Application.php @@ -14,6 +14,8 @@ use App\Traits\WithAuthorization; use App\Traits\WithToast; use App\Traits\WithUpdatedData; +use Illuminate\Contracts\View\View; +use Illuminate\Support\Facades\Artisan; use Livewire\Attributes\Title; use Livewire\Component; @@ -35,7 +37,7 @@ public function mount( ContactSettings $contactSettings, SocialSettings $socialSettings, SeoSettings $seoSettings, - ) { + ): void { $this->generalForm->setSettings($generalSettings); $this->contactForm->setSettings($contactSettings); @@ -45,16 +47,26 @@ public function mount( $this->seoForm->setSettings($seoSettings); } - public function updateGeneral(GeneralSettings $generalSettings) + public function updateGeneral(GeneralSettings $generalSettings): void { $this->canOrAbort('update general settings'); - $this->generalForm->update($generalSettings); + $isMaintenance = $this->generalForm->update($generalSettings); + + if ($isMaintenance) { + Artisan::call('down', [ + '--secret' => config('app.maintenance.secret'), + ]); + + $this->redirect('/'.config('app.maintenance.secret')); + } + + Artisan::call('up'); $this->toast('Pengaturan umum berhasil diperbarui.'); } - public function updateContact(ContactSettings $contactSettings) + public function updateContact(ContactSettings $contactSettings): void { $this->canOrAbort('update contact settings'); @@ -63,7 +75,7 @@ public function updateContact(ContactSettings $contactSettings) $this->toast('Pengaturan kontak berhasil diperbarui.'); } - public function updateSocial(SocialSettings $socialSettings) + public function updateSocial(SocialSettings $socialSettings): void { $this->canOrAbort('update social settings'); @@ -72,7 +84,7 @@ public function updateSocial(SocialSettings $socialSettings) $this->toast('Pengaturan media sosial berhasil diperbarui.'); } - public function updateSeo(SeoSettings $seoSettings) + public function updateSeo(SeoSettings $seoSettings): void { $this->canOrAbort('update seo settings'); @@ -81,7 +93,7 @@ public function updateSeo(SeoSettings $seoSettings) $this->toast('Pengaturan SEO berhasil diperbarui.'); } - public function render() + public function render(): View { return view('livewire.studio.setting.application', [ 'pageTitle' => 'Aplikasi', diff --git a/config/app.php b/config/app.php index c74a68d..12cddb7 100644 --- a/config/app.php +++ b/config/app.php @@ -121,6 +121,7 @@ 'maintenance' => [ 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 'store' => env('APP_MAINTENANCE_STORE', 'database'), + 'secret' => env('APP_MAINTENANCE_SECRET'), ], ]; diff --git a/tests/Feature/Livewire/Studio/Setting/ApplicationTest.php b/tests/Feature/Livewire/Studio/Setting/ApplicationTest.php new file mode 100644 index 0000000..f296a0f --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/ApplicationTest.php @@ -0,0 +1,301 @@ +seed(RolePermissionSeeder::class); + + $this->user = User::factory() + ->active() + ->create(); + + $this->ownerRole = Role::where('name', 'Owner')->first(); + + $this->user->roles()->attach($this->ownerRole->id); +}); + +function mountApplicationComponent(User $user) +{ + return Livewire::actingAs($user)->test(Application::class); +} + +/* +|-------------------------------------------------------------------------- +| Component Rendering & Mount +|-------------------------------------------------------------------------- +*/ + +it('renders page successfully', function () { + mountApplicationComponent($this->user) + ->assertViewIs('livewire.studio.setting.application') + ->assertViewHas('pageTitle', 'Aplikasi'); +}); + +it('mounts with settings loaded', function () { + $component = mountApplicationComponent($this->user); + + expect($component->generalForm->site_name)->toBe('Yadi Parfum'); + expect($component->contactForm->phone_number)->toBe('083816931711'); + expect($component->socialForm->whatsapp)->toBe('https://wa.me/6283816931711'); + expect($component->seoForm->meta_description)->toBe('Nikmati pengalaman wangi berkualitas tinggi dengan parfum refill dan produk unggulan dari Yadi Parfum.'); +}); + +/* +|-------------------------------------------------------------------------- +| General Settings Update +|-------------------------------------------------------------------------- +*/ + +it('updates general settings successfully', function () { + mountApplicationComponent($this->user) + ->set('generalForm.site_name', 'Updated Site Name') + ->set('generalForm.site_tagline', 'Updated Tagline') + ->set('generalForm.site_description', 'Updated description for the site') + ->set('generalForm.maintenance_mode', true) + ->call('updateGeneral') + ->assertHasNoErrors(); + + $settings = app(GeneralSettings::class); + expect($settings->site_name)->toBe('Updated Site Name'); + expect($settings->site_tagline)->toBe('Updated Tagline'); + expect($settings->site_description)->toBe('Updated description for the site'); + expect($settings->maintenance_mode)->toBe(true); +}); + +it('validates general settings required fields', function () { + mountApplicationComponent($this->user) + ->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('validates general settings field lengths', function () { + mountApplicationComponent($this->user) + ->set('generalForm.site_name', str_repeat('a', 101)) + ->set('generalForm.site_tagline', str_repeat('a', 151)) + ->call('updateGeneral') + ->assertHasErrors([ + 'generalForm.site_name' => 'max', + 'generalForm.site_tagline' => 'max', + ]); +}); + +/* +|-------------------------------------------------------------------------- +| Contact Settings Update +|-------------------------------------------------------------------------- +*/ + +it('updates contact settings successfully', function () { + mountApplicationComponent($this->user) + ->set('contactForm.phone_number', '0812 3456 7890') + ->set('contactForm.email', 'contact@example.com') + ->set('contactForm.address', 'Updated Address 123') + ->set('contactForm.map_embed', '') + ->call('updateContact') + ->assertHasNoErrors(); + + $settings = app(ContactSettings::class); + + expect($settings->phone_number)->toBe('0812 3456 7890'); + expect($settings->email)->toBe('contact@example.com'); + expect($settings->address)->toBe('Updated Address 123'); + expect($settings->map_embed)->toBe(''); +}); + +it('validates contact settings required fields', function () { + mountApplicationComponent($this->user) + ->set('contactForm.phone_number', '') + ->set('contactForm.address', '') + ->call('updateContact') + ->assertHasErrors([ + 'contactForm.phone_number' => 'required', + 'contactForm.address' => 'required', + ]); +}); + +it('validates contact settings email format', function () { + mountApplicationComponent($this->user) + ->set('contactForm.phone_number', '+628123456789') + ->set('contactForm.email', 'invalid-email') + ->set('contactForm.address', 'Valid Address') + ->call('updateContact') + ->assertHasErrors([ + 'contactForm.email' => 'email', + ]); +}); + +/* +|-------------------------------------------------------------------------- +| Social Settings Update +|-------------------------------------------------------------------------- +*/ + +it('updates social settings successfully', function () { + mountApplicationComponent($this->user) + ->set('socialForm.facebook', 'https://facebook.com/page') + ->set('socialForm.instagram', 'https://instagram.com/page') + ->set('socialForm.youtube', 'https://youtube.com/channel') + ->set('socialForm.tiktok', 'https://tiktok.com/@user') + ->set('socialForm.whatsapp', 'https://wa.me/123456789') + ->set('socialForm.telegram', 'https://t.me/channel') + ->call('updateSocial') + ->assertHasNoErrors(); + + $settings = app(SocialSettings::class); + expect($settings->facebook)->toBe('https://facebook.com/page'); + expect($settings->instagram)->toBe('https://instagram.com/page'); + expect($settings->youtube)->toBe('https://youtube.com/channel'); + expect($settings->tiktok)->toBe('https://tiktok.com/@user'); + expect($settings->whatsapp)->toBe('https://wa.me/123456789'); + expect($settings->telegram)->toBe('https://t.me/channel'); +}); + +it('validates social settings url format', function () { + mountApplicationComponent($this->user) + ->set('socialForm.facebook', 'not-a-url') + ->call('updateSocial') + ->assertHasErrors([ + 'socialForm.facebook' => 'url', + ]); +}); + +/* +|-------------------------------------------------------------------------- +| SEO Settings Update +|-------------------------------------------------------------------------- +*/ + +it('updates seo settings successfully', function () { + mountApplicationComponent($this->user) + ->set('seoForm.meta_description', 'Updated meta description for SEO') + ->set('seoForm.meta_keywords', 'keyword1, keyword2, keyword3') + ->set('seoForm.meta_author', 'Updated Author') + ->set('seoForm.og_image', 'https://example.com/image.jpg') + ->set('seoForm.og_title', 'Open Graph Title') + ->set('seoForm.og_description', 'Open Graph Description') + ->set('seoForm.og_type', 'article') + ->set('seoForm.twitter_card', 'summary_large_image') + ->set('seoForm.canonical_url', 'https://example.com/page') + ->call('updateSeo') + ->assertHasNoErrors(); + + $settings = app(SeoSettings::class); + expect($settings->meta_description)->toBe('Updated meta description for SEO'); + expect($settings->meta_keywords)->toBe('keyword1, keyword2, keyword3'); + expect($settings->meta_author)->toBe('Updated Author'); + expect($settings->og_image)->toBe('https://example.com/image.jpg'); + expect($settings->og_title)->toBe('Open Graph Title'); + expect($settings->og_description)->toBe('Open Graph Description'); + expect($settings->og_type)->toBe('article'); + expect($settings->twitter_card)->toBe('summary_large_image'); + expect($settings->canonical_url)->toBe('https://example.com/page'); +}); + +it('validates seo settings required fields', function () { + mountApplicationComponent($this->user) + ->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('validates seo settings og_type enum', function () { + mountApplicationComponent($this->user) + ->set('seoForm.meta_description', 'Valid description') + ->set('seoForm.meta_keywords', 'keywords') + ->set('seoForm.meta_author', 'Author') + ->set('seoForm.og_type', 'invalid_type') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.og_type' => 'in', + ]); +}); + +it('validates seo settings twitter_card enum', function () { + mountApplicationComponent($this->user) + ->set('seoForm.meta_description', 'Valid description') + ->set('seoForm.meta_keywords', 'keywords') + ->set('seoForm.meta_author', 'Author') + ->set('seoForm.twitter_card', 'invalid_card') + ->call('updateSeo') + ->assertHasErrors([ + 'seoForm.twitter_card' => 'in', + ]); +}); + +/* +|-------------------------------------------------------------------------- +| Authorization Tests +|-------------------------------------------------------------------------- +*/ + +it('requires update general settings permission', function () { + // Temporarily remove the permission from the user + $this->ownerRole->revokePermissionTo('update general settings'); + + mountApplicationComponent($this->user) + ->call('updateGeneral') + ->assertForbidden(); + + // Restore the permission + $this->user->givePermissionTo('update general settings'); +}); + +it('requires update contact settings permission', function () { + // Temporarily remove the permission from the user + $this->ownerRole->revokePermissionTo('update contact settings'); + + mountApplicationComponent($this->user) + ->call('updateContact') + ->assertForbidden(); + + // Restore the permission + $this->user->givePermissionTo('update contact settings'); +}); + +it('requires update social settings permission', function () { + // Temporarily remove the permission from the user + $this->ownerRole->revokePermissionTo('update social settings'); + + mountApplicationComponent($this->user) + ->call('updateSocial') + ->assertForbidden(); + + // Restore the permission + $this->user->givePermissionTo('update social settings'); +}); + +it('requires update seo settings permission', function () { + // Temporarily remove the permission from the user + $this->ownerRole->revokePermissionTo('update seo settings'); + + mountApplicationComponent($this->user) + ->call('updateSeo') + ->assertForbidden(); + + // Restore the permission + $this->user->givePermissionTo('update seo settings'); +}); diff --git a/tests/Feature/Livewire/Studio/Setting/ContactFormTest.php b/tests/Feature/Livewire/Studio/Setting/ContactFormTest.php new file mode 100644 index 0000000..229457c --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/ContactFormTest.php @@ -0,0 +1,201 @@ +form = new ContactForm($this, 'form'); + } + + public function render() + { + return '
'; + } + }; + + $component->mount(); + + return $component->form; +} + +it('has correct validation rules', function () { + $form = createContactForm(); + $rules = $form->rules(); + + expect($rules)->toHaveKey('phone_number'); + expect($rules)->toHaveKey('email'); + expect($rules)->toHaveKey('address'); + expect($rules)->toHaveKey('map_embed'); + + expect($rules['phone_number'])->toContain('required'); + expect($rules['phone_number'])->toContain('string'); + expect($rules['phone_number'])->toContain('min:10'); + expect($rules['phone_number'])->toContain('max:20'); + + expect($rules['email'])->toContain('nullable'); + expect($rules['email'])->toContain('email'); + expect($rules['email'])->toContain('max:100'); + + expect($rules['address'])->toContain('required'); + expect($rules['address'])->toContain('string'); + expect($rules['address'])->toContain('min:10'); + expect($rules['address'])->toContain('max:500'); + + expect($rules['map_embed'])->toContain('nullable'); + expect($rules['map_embed'])->toContain('string'); + expect($rules['map_embed'])->toContain('max:2000'); +}); + +it('validates required fields', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '', + 'address' => '', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('phone_number'))->toBeTrue(); + expect($validator->errors()->has('address'))->toBeTrue(); +}); + +it('validates phone number format', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => 'invalid', + 'address' => 'Valid Address', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('phone_number'))->toBeTrue(); +}); + +it('validates email format', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '0812 3456 789', + 'email' => 'invalid-email', + 'address' => 'Valid Address', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('email'))->toBeTrue(); +}); + +it('validates phone number length', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '123', + 'address' => 'Valid Address', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('phone_number'))->toBeTrue(); +}); + +it('validates address length', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '+628123456789', + 'address' => 'Short', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('address'))->toBeTrue(); +}); + +it('validates email max length', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '+628123456789', + 'email' => str_repeat('a', 101).'@example.com', + 'address' => 'Valid Address', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('email'))->toBeTrue(); +}); + +it('validates map embed max length', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '+628123456789', + 'address' => 'Valid Address', + 'map_embed' => str_repeat('a', 2001), + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('map_embed'))->toBeTrue(); +}); + +it('accepts nullable email', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '0812 3456 789', + 'email' => null, + 'address' => 'Valid Address', + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('accepts nullable map embed', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '0812 3456 789', + 'address' => 'Valid Address', + 'map_embed' => null, + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('passes validation with valid data', function () { + $form = createContactForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'phone_number' => '0812 3456 789', + 'email' => 'test@example.com', + 'address' => 'Valid Address with proper length', + 'map_embed' => '', + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('has correct validation attributes', function () { + $form = createContactForm(); + $attributes = $form->validationAttributes(); + + expect($attributes)->toHaveKey('phone_number', 'nomor telepon'); + expect($attributes)->toHaveKey('email', 'email'); + expect($attributes)->toHaveKey('address', 'alamat'); + expect($attributes)->toHaveKey('map_embed', 'embed peta'); +}); diff --git a/tests/Feature/Livewire/Studio/Setting/GeneralFormTest.php b/tests/Feature/Livewire/Studio/Setting/GeneralFormTest.php new file mode 100644 index 0000000..4be197b --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/GeneralFormTest.php @@ -0,0 +1,143 @@ +form = new GeneralForm($this, 'form'); + } + + public function render() + { + return '
'; + } + }; + + $component->mount(); + + return $component->form; +} + +it('has correct validation rules', function () { + $form = createGeneralForm(); + $rules = $form->rules(); + + expect($rules)->toHaveKey('site_name'); + expect($rules)->toHaveKey('site_tagline'); + expect($rules)->toHaveKey('site_description'); + expect($rules)->toHaveKey('maintenance_mode'); + + expect($rules['site_name'])->toContain('required'); + expect($rules['site_name'])->toContain('string'); + expect($rules['site_name'])->toContain('max:100'); + expect($rules['site_name'])->toContain('min:1'); + + expect($rules['site_tagline'])->toContain('required'); + expect($rules['site_tagline'])->toContain('string'); + expect($rules['site_tagline'])->toContain('max:150'); + expect($rules['site_tagline'])->toContain('min:1'); + + expect($rules['site_description'])->toContain('required'); + expect($rules['site_description'])->toContain('string'); + expect($rules['site_description'])->toContain('min:10'); + expect($rules['site_description'])->toContain('max:500'); + + expect($rules['maintenance_mode'])->toContain('required'); + expect($rules['maintenance_mode'])->toContain('boolean'); +}); + +it('validates required fields', function () { + $form = createGeneralForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'site_name' => '', + 'site_tagline' => '', + 'site_description' => '', + 'maintenance_mode' => false, + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('site_name'))->toBeTrue(); + expect($validator->errors()->has('site_tagline'))->toBeTrue(); + expect($validator->errors()->has('site_description'))->toBeTrue(); +}); + +it('validates minimum lengths', function () { + $form = createGeneralForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'site_name' => 'A', + 'site_tagline' => 'B', + 'site_description' => 'Short', + 'maintenance_mode' => false, + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('site_description'))->toBeTrue(); +}); + +it('validates maximum lengths', function () { + $form = createGeneralForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'site_name' => str_repeat('A', 101), + 'site_tagline' => str_repeat('B', 151), + 'site_description' => 'Valid description', + 'maintenance_mode' => false, + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('site_name'))->toBeTrue(); + expect($validator->errors()->has('site_tagline'))->toBeTrue(); +}); + +it('validates maintenance mode boolean', function () { + $form = createGeneralForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'site_name' => 'Valid Name', + 'site_tagline' => 'Valid Tagline', + 'site_description' => 'Valid description with proper length', + 'maintenance_mode' => 'not_boolean', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('maintenance_mode'))->toBeTrue(); +}); + +it('passes validation with valid data', function () { + $form = createGeneralForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'site_name' => 'Valid Site Name', + 'site_tagline' => 'Valid Tagline', + 'site_description' => 'Valid description with proper length for the site', + 'maintenance_mode' => true, + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('has correct validation attributes', function () { + $form = createGeneralForm(); + $attributes = $form->validationAttributes(); + + expect($attributes)->toHaveKey('site_name', 'nama situs'); + expect($attributes)->toHaveKey('site_tagline', 'tagline situs'); + expect($attributes)->toHaveKey('site_description', 'deskripsi situs'); + expect($attributes)->toHaveKey('maintenance_mode', 'mode maintenance'); +}); diff --git a/tests/Feature/Livewire/Studio/Setting/SeoFormTest.php b/tests/Feature/Livewire/Studio/Setting/SeoFormTest.php new file mode 100644 index 0000000..81b411c --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/SeoFormTest.php @@ -0,0 +1,350 @@ +form = new SeoForm($this, 'form'); + } + + public function render() + { + return '
'; + } + }; + + $component->mount(); + + return $component->form; +} + +it('has correct validation rules', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + expect($rules)->toHaveKey('meta_description'); + expect($rules)->toHaveKey('meta_keywords'); + expect($rules)->toHaveKey('meta_author'); + expect($rules)->toHaveKey('og_image'); + expect($rules)->toHaveKey('og_title'); + expect($rules)->toHaveKey('og_description'); + expect($rules)->toHaveKey('og_type'); + expect($rules)->toHaveKey('twitter_card'); + expect($rules)->toHaveKey('canonical_url'); + + expect($rules['meta_description'])->toContain('required'); + expect($rules['meta_description'])->toContain('string'); + expect($rules['meta_description'])->toContain('min:10'); + expect($rules['meta_description'])->toContain('max:160'); + + expect($rules['meta_keywords'])->toContain('required'); + expect($rules['meta_keywords'])->toContain('string'); + expect($rules['meta_keywords'])->toContain('min:3'); + expect($rules['meta_keywords'])->toContain('max:255'); + + expect($rules['meta_author'])->toContain('required'); + expect($rules['meta_author'])->toContain('string'); + expect($rules['meta_author'])->toContain('min:1'); + expect($rules['meta_author'])->toContain('max:150'); + + expect($rules['og_image'])->toContain('nullable'); + expect($rules['og_image'])->toContain('string'); + expect($rules['og_image'])->toContain('max:500'); + expect($rules['og_image'])->toContain('url'); + + expect($rules['og_title'])->toContain('nullable'); + expect($rules['og_title'])->toContain('string'); + expect($rules['og_title'])->toContain('max:255'); + + expect($rules['og_description'])->toContain('nullable'); + expect($rules['og_description'])->toContain('string'); + expect($rules['og_description'])->toContain('max:300'); + + expect($rules['og_type'])->toContain('nullable'); + expect($rules['og_type'])->toContain('string'); + expect($rules['og_type'])->toContain('max:50'); + expect($rules['og_type'])->toContain('in:website,article,product'); + + expect($rules['twitter_card'])->toContain('nullable'); + expect($rules['twitter_card'])->toContain('string'); + expect($rules['twitter_card'])->toContain('max:50'); + expect($rules['twitter_card'])->toContain('in:summary,summary_large_image'); + + expect($rules['canonical_url'])->toContain('nullable'); + expect($rules['canonical_url'])->toContain('string'); + expect($rules['canonical_url'])->toContain('max:500'); + expect($rules['canonical_url'])->toContain('url'); +}); + +it('validates required fields', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => '', + 'meta_keywords' => '', + 'meta_author' => '', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('meta_description'))->toBeTrue(); + expect($validator->errors()->has('meta_keywords'))->toBeTrue(); + expect($validator->errors()->has('meta_author'))->toBeTrue(); +}); + +it('validates meta description length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Short', + 'meta_keywords' => 'keywords', + 'meta_author' => 'Author', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('meta_description'))->toBeTrue(); +}); + +it('validates meta description max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => str_repeat('a', 161), + 'meta_keywords' => 'keywords', + 'meta_author' => 'Author', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('meta_description'))->toBeTrue(); +}); + +it('validates meta keywords minimum length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'ab', + 'meta_author' => 'Author', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('meta_keywords'))->toBeTrue(); +}); + +it('validates meta author max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => str_repeat('a', 151), + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('meta_author'))->toBeTrue(); +}); + +it('validates og_image url format and max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_image' => 'not-a-url', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('og_image'))->toBeTrue(); +}); + +it('validates og_image max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_image' => 'https://example.com/'.str_repeat('a', 501), + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('og_image'))->toBeTrue(); +}); + +it('validates og_title max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_title' => str_repeat('a', 256), + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('og_title'))->toBeTrue(); +}); + +it('validates og_description max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_description' => str_repeat('a', 301), + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('og_description'))->toBeTrue(); +}); + +it('validates og_type enum values', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_type' => 'invalid_type', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('og_type'))->toBeTrue(); +}); + +it('accepts valid og_type values', function () { + $form = createSeoForm(); + $rules = $form->rules(); + $validTypes = ['website', 'article', 'product']; + + foreach ($validTypes as $type) { + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_type' => $type, + ], $rules); + + expect($validator->passes())->toBeTrue(); + } +}); + +it('validates twitter_card enum values', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'twitter_card' => 'invalid_card', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('twitter_card'))->toBeTrue(); +}); + +it('accepts valid twitter_card values', function () { + $form = createSeoForm(); + $rules = $form->rules(); + $validCards = ['summary', 'summary_large_image']; + + foreach ($validCards as $card) { + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'twitter_card' => $card, + ], $rules); + + expect($validator->passes())->toBeTrue(); + } +}); + +it('validates canonical_url url format and max length', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'canonical_url' => 'not-a-url', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('canonical_url'))->toBeTrue(); +}); + +it('accepts nullable fields', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description with proper length', + 'meta_keywords' => 'valid keywords', + 'meta_author' => 'Author', + 'og_image' => null, + 'og_title' => null, + 'og_description' => null, + 'og_type' => null, + 'twitter_card' => null, + 'canonical_url' => null, + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('passes validation with valid data', function () { + $form = createSeoForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'meta_description' => 'Valid meta description for SEO purposes', + 'meta_keywords' => 'keyword1, keyword2, keyword3', + 'meta_author' => 'Valid Author', + 'og_image' => 'https://example.com/image.jpg', + 'og_title' => 'Valid OG Title', + 'og_description' => 'Valid OG Description', + 'og_type' => 'article', + 'twitter_card' => 'summary_large_image', + 'canonical_url' => 'https://example.com/page', + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('has correct validation attributes', function () { + $form = createSeoForm(); + $attributes = $form->validationAttributes(); + + expect($attributes)->toHaveKey('meta_description', 'meta deskripsi'); + expect($attributes)->toHaveKey('meta_keywords', 'meta kata kunci'); + expect($attributes)->toHaveKey('meta_author', 'meta penulis'); + expect($attributes)->toHaveKey('og_image', 'gambar Open Graph'); + expect($attributes)->toHaveKey('og_title', 'judul Open Graph'); + expect($attributes)->toHaveKey('og_description', 'deskripsi Open Graph'); + expect($attributes)->toHaveKey('og_type', 'tipe Open Graph'); + expect($attributes)->toHaveKey('twitter_card', 'kartu Twitter'); + expect($attributes)->toHaveKey('canonical_url', 'URL kanonis'); +}); diff --git a/tests/Feature/Livewire/Studio/Setting/SocialFormTest.php b/tests/Feature/Livewire/Studio/Setting/SocialFormTest.php new file mode 100644 index 0000000..7381ff6 --- /dev/null +++ b/tests/Feature/Livewire/Studio/Setting/SocialFormTest.php @@ -0,0 +1,117 @@ +form = new SocialForm($this, 'form'); + } + + public function render() + { + return '
'; + } + }; + + $component->mount(); + + return $component->form; +} + +it('has correct validation rules', function () { + $form = createSocialForm(); + $rules = $form->rules(); + + expect($rules)->toHaveKey('facebook'); + expect($rules)->toHaveKey('instagram'); + expect($rules)->toHaveKey('youtube'); + expect($rules)->toHaveKey('tiktok'); + expect($rules)->toHaveKey('whatsapp'); + expect($rules)->toHaveKey('telegram'); + + foreach (['facebook', 'instagram', 'youtube', 'tiktok', 'whatsapp', 'telegram'] as $field) { + expect($rules[$field])->toContain('nullable'); + expect($rules[$field])->toContain('url'); + expect($rules[$field])->toContain('max:255'); + } +}); + +it('validates nullable fields accept null values', function () { + $form = createSocialForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'facebook' => null, + 'instagram' => null, + 'youtube' => null, + 'tiktok' => null, + 'whatsapp' => null, + 'telegram' => null, + ], $rules); + + expect($validator->passes())->toBeTrue(); +}); + +it('validates url format for social media fields', function () { + $form = createSocialForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'facebook' => 'not-a-url', + 'instagram' => 'https://instagram.com/valid', + 'youtube' => 'invalid-url', + 'tiktok' => 'https://tiktok.com/@valid', + 'whatsapp' => 'not-a-url', + 'telegram' => 'https://t.me/valid', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('facebook'))->toBeTrue(); + expect($validator->errors()->has('youtube'))->toBeTrue(); + expect($validator->errors()->has('whatsapp'))->toBeTrue(); + expect($validator->errors()->has('instagram'))->toBeFalse(); + expect($validator->errors()->has('tiktok'))->toBeFalse(); + expect($validator->errors()->has('telegram'))->toBeFalse(); +}); + +it('validates url max length', function () { + $form = createSocialForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'facebook' => 'https://facebook.com/'.str_repeat('a', 256), + 'instagram' => 'https://instagram.com/valid', + 'youtube' => 'https://youtube.com/valid', + 'tiktok' => 'https://tiktok.com/@valid', + 'whatsapp' => 'https://wa.me/valid', + 'telegram' => 'https://t.me/valid', + ], $rules); + + expect($validator->fails())->toBeTrue(); + expect($validator->errors()->has('facebook'))->toBeTrue(); +}); + +it('accepts valid urls', function () { + $form = createSocialForm(); + $rules = $form->rules(); + + $validator = Validator::make([ + 'facebook' => 'https://facebook.com/page', + 'instagram' => 'https://instagram.com/page', + 'youtube' => 'https://youtube.com/channel', + 'tiktok' => 'https://tiktok.com/@user', + 'whatsapp' => 'https://wa.me/123456789', + 'telegram' => 'https://t.me/channel', + ], $rules); + + expect($validator->passes())->toBeTrue(); +});