feat(setting): implementasi maintenance mode

- membuat config baru untuk secret key
- implementasi test
- menyesuaikan validasi
- kasih return type
This commit is contained in:
Yoga Pangestu 2025-12-10 11:38:44 +07:00
parent 2b5983fb2a
commit 224ea30a2a
11 changed files with 1186 additions and 34 deletions

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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',
];
}

View File

@ -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'],
];
}

View File

@ -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',

View File

@ -121,6 +121,7 @@
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
'secret' => env('APP_MAINTENANCE_SECRET'),
],
];

View File

@ -0,0 +1,301 @@
<?php
use App\Livewire\Studio\Setting\Application;
use App\Models\User;
use App\Settings\ContactSettings;
use App\Settings\GeneralSettings;
use App\Settings\SeoSettings;
use App\Settings\SocialSettings;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->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', '<iframe>map</iframe>')
->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('<iframe>map</iframe>');
});
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');
});

View File

@ -0,0 +1,201 @@
<?php
use App\Livewire\Forms\Studio\Setting\ContactForm;
use Illuminate\Support\Facades\Validator;
use Livewire\Livewire;
function createContactForm()
{
// Create a minimal Livewire component for testing
$component = new class extends \Livewire\Component
{
public ContactForm $form;
public function mount()
{
$this->form = new ContactForm($this, 'form');
}
public function render()
{
return '<div></div>';
}
};
$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' => '<iframe>valid embed</iframe>',
], $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');
});

View File

@ -0,0 +1,143 @@
<?php
use App\Livewire\Forms\Studio\Setting\GeneralForm;
use Illuminate\Support\Facades\Validator;
use Livewire\Livewire;
function createGeneralForm()
{
// Create a minimal Livewire component for testing
$component = new class extends \Livewire\Component
{
public GeneralForm $form;
public function mount()
{
$this->form = new GeneralForm($this, 'form');
}
public function render()
{
return '<div></div>';
}
};
$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');
});

View File

@ -0,0 +1,350 @@
<?php
use App\Livewire\Forms\Studio\Setting\SeoForm;
use Illuminate\Support\Facades\Validator;
use Livewire\Livewire;
function createSeoForm()
{
// Create a minimal Livewire component for testing
$component = new class extends \Livewire\Component
{
public SeoForm $form;
public function mount()
{
$this->form = new SeoForm($this, 'form');
}
public function render()
{
return '<div></div>';
}
};
$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');
});

View File

@ -0,0 +1,117 @@
<?php
use App\Livewire\Forms\Studio\Setting\SocialForm;
use Illuminate\Support\Facades\Validator;
use Livewire\Livewire;
function createSocialForm()
{
// Create a minimal Livewire component for testing
$component = new class extends \Livewire\Component
{
public SocialForm $form;
public function mount()
{
$this->form = new SocialForm($this, 'form');
}
public function render()
{
return '<div></div>';
}
};
$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();
});