serviceData = [ [ 'id' => 1, 'code' => 'social', 'module' => 'Core\\Mod\\Social', 'name' => 'Social', 'tagline' => 'Social media management', 'description' => 'Full social media management platform.', 'icon' => 'fa-share-nodes', 'color' => 'blue', 'marketing_domain' => 'social.lthn.sh', 'marketing_url' => 'https://social.lthn.sh', 'docs_url' => 'https://docs.lthn.sh/social', 'is_enabled' => true, 'is_public' => true, 'is_featured' => true, 'entitlement_code' => 'core.srv.social', 'sort_order' => 10, ], [ 'id' => 2, 'code' => 'analytics', 'module' => 'Core\\Mod\\Analytics', 'name' => 'Analytics', 'tagline' => 'Privacy-focused analytics', 'description' => 'Website analytics without cookies.', 'icon' => 'fa-chart-line', 'color' => 'cyan', 'marketing_domain' => '', 'marketing_url' => '', 'docs_url' => '', 'is_enabled' => true, 'is_public' => true, 'is_featured' => false, 'entitlement_code' => 'core.srv.analytics', 'sort_order' => 20, ], [ 'id' => 3, 'code' => 'legacy-api', 'module' => 'Core\\Mod\\Legacy', 'name' => 'Legacy API', 'tagline' => null, 'description' => null, 'icon' => '', 'color' => '', 'marketing_domain' => '', 'marketing_url' => '', 'docs_url' => '', 'is_enabled' => false, 'is_public' => false, 'is_featured' => false, 'entitlement_code' => '', 'sort_order' => 99, ], ]; } protected function rules(): array { return [ 'name' => ['required', 'string', 'max:100'], 'tagline' => ['nullable', 'string', 'max:200'], 'description' => ['nullable', 'string', 'max:2000'], 'icon' => ['nullable', 'string', 'max:50'], 'color' => ['nullable', 'string', 'max:20'], 'marketing_domain' => ['nullable', 'string', 'max:100'], 'marketing_url' => ['nullable', 'url', 'max:255'], 'docs_url' => ['nullable', 'url', 'max:255'], 'is_enabled' => ['boolean'], 'is_public' => ['boolean'], 'is_featured' => ['boolean'], 'sort_order' => ['integer', 'min:0', 'max:999'], ]; } public function openEdit(int $id): void { $service = collect($this->serviceData)->firstWhere('id', $id); if (! $service) { return; } $this->editingId = $id; // Read-only fields $this->code = $service['code']; $this->module = $service['module']; $this->entitlement_code = $service['entitlement_code'] ?? ''; // Editable fields $this->name = $service['name']; $this->tagline = $service['tagline'] ?? ''; $this->description = $service['description'] ?? ''; $this->icon = $service['icon'] ?? ''; $this->color = $service['color'] ?? ''; $this->marketing_domain = $service['marketing_domain'] ?? ''; $this->marketing_url = $service['marketing_url'] ?? ''; $this->docs_url = $service['docs_url'] ?? ''; $this->is_enabled = $service['is_enabled']; $this->is_public = $service['is_public']; $this->is_featured = $service['is_featured']; $this->sort_order = $service['sort_order']; $this->showModal = true; } public function save(): void { $this->validate(); // Simulate updating the service in our data array $this->serviceData = collect($this->serviceData)->map(function ($s) { if ($s['id'] === $this->editingId) { $s['name'] = $this->name; $s['tagline'] = $this->tagline ?: null; $s['description'] = $this->description ?: null; $s['icon'] = $this->icon ?: null; $s['color'] = $this->color ?: null; $s['marketing_domain'] = $this->marketing_domain ?: null; $s['marketing_url'] = $this->marketing_url ?: null; $s['docs_url'] = $this->docs_url ?: null; $s['is_enabled'] = $this->is_enabled; $s['is_public'] = $this->is_public; $s['is_featured'] = $this->is_featured; $s['sort_order'] = $this->sort_order; } return $s; })->all(); $this->flashMessages[] = 'Service updated successfully.'; $this->closeModal(); } public function toggleEnabled(int $id): void { $this->serviceData = collect($this->serviceData)->map(function ($s) use ($id) { if ($s['id'] === $id) { $s['is_enabled'] = ! $s['is_enabled']; $status = $s['is_enabled'] ? 'enabled' : 'disabled'; $this->flashMessages[] = "{$s['name']} has been {$status}."; } return $s; })->all(); } public function syncFromModules(): void { $this->flashMessages[] = 'Services synced from modules successfully.'; } public function closeModal(): void { $this->showModal = false; $this->resetForm(); } protected function resetForm(): void { $this->editingId = null; $this->code = ''; $this->module = ''; $this->entitlement_code = ''; $this->name = ''; $this->tagline = ''; $this->description = ''; $this->icon = ''; $this->color = ''; $this->marketing_domain = ''; $this->marketing_url = ''; $this->docs_url = ''; $this->is_enabled = true; $this->is_public = true; $this->is_featured = false; $this->sort_order = 50; } #[Computed] public function services(): array { return $this->serviceData; } #[Computed] public function tableColumns(): array { return [ 'Service', 'Code', 'Domain', ['label' => 'Entitlement', 'align' => 'center'], ['label' => 'Status', 'align' => 'center'], ['label' => 'Actions', 'align' => 'center'], ]; } #[Computed] public function enabledCount(): int { return count(array_filter($this->serviceData, fn ($s) => $s['is_enabled'])); } #[Computed] public function featuredCount(): int { return count(array_filter($this->serviceData, fn ($s) => $s['is_featured'])); } public function render(): string { return <<<'HTML'
Services: {{ count($this->services) }} Enabled: {{ $this->enabledCount }} Featured: {{ $this->featuredCount }} @if($showModal)

Edit: {{ $name }}

Code: {{ $code }} Module: {{ $module }}
@endif @foreach($flashMessages as $msg)
{{ $msg }}
@endforeach
HTML; } } // ============================================================================= // Initial State Tests // ============================================================================= describe('ServiceManager initial state', function () { it('starts with modal closed', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSet('showModal', false) ->assertSet('editingId', null); }); it('loads service data on mount', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSet('serviceData', fn ($data) => count($data) === 3); }); it('shows service count', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSee('Services: 3'); }); it('counts enabled services correctly', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSee('Enabled: 2'); }); it('counts featured services correctly', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSee('Featured: 1'); }); it('has correct default form values', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSet('is_enabled', true) ->assertSet('is_public', true) ->assertSet('is_featured', false) ->assertSet('sort_order', 50); }); }); // ============================================================================= // Open Edit Modal Tests // ============================================================================= describe('ServiceManager edit modal opening', function () { it('opens modal and populates all fields', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->assertSet('showModal', true) ->assertSet('editingId', 1) ->assertSet('name', 'Social') ->assertSet('tagline', 'Social media management') ->assertSet('code', 'social') ->assertSet('module', 'Core\\Mod\\Social') ->assertSet('entitlement_code', 'core.srv.social') ->assertSet('icon', 'fa-share-nodes') ->assertSet('color', 'blue') ->assertSet('marketing_domain', 'social.lthn.sh') ->assertSet('marketing_url', 'https://social.lthn.sh') ->assertSet('docs_url', 'https://docs.lthn.sh/social') ->assertSet('is_enabled', true) ->assertSet('is_public', true) ->assertSet('is_featured', true) ->assertSet('sort_order', 10); }); it('renders edit modal with service name', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->assertSee('Edit: Social') ->assertSee('Code: social') ->assertSee('Module: Core\\Mod\\Social'); }); it('populates fields for service with empty optional fields', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 3) ->assertSet('showModal', true) ->assertSet('name', 'Legacy API') ->assertSet('tagline', '') ->assertSet('description', '') ->assertSet('icon', '') ->assertSet('marketing_domain', '') ->assertSet('is_enabled', false) ->assertSet('is_public', false) ->assertSet('is_featured', false) ->assertSet('sort_order', 99); }); it('does nothing for non-existent service id', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 999) ->assertSet('showModal', false) ->assertSet('editingId', null); }); it('can open different services sequentially', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->assertSet('name', 'Social') ->call('closeModal') ->call('openEdit', 2) ->assertSet('name', 'Analytics') ->assertSet('code', 'analytics'); }); }); // ============================================================================= // Close Modal and Form Reset Tests // ============================================================================= describe('ServiceManager close modal', function () { it('closes modal and resets all form fields', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->call('closeModal') ->assertSet('showModal', false) ->assertSet('editingId', null) ->assertSet('code', '') ->assertSet('module', '') ->assertSet('entitlement_code', '') ->assertSet('name', '') ->assertSet('tagline', '') ->assertSet('description', '') ->assertSet('icon', '') ->assertSet('color', '') ->assertSet('marketing_domain', '') ->assertSet('marketing_url', '') ->assertSet('docs_url', '') ->assertSet('is_enabled', true) ->assertSet('is_public', true) ->assertSet('is_featured', false) ->assertSet('sort_order', 50); }); it('does not show modal content after closing', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->assertSee('Edit: Social') ->call('closeModal') ->assertDontSee('Edit: Social'); }); }); // ============================================================================= // Form Validation Tests // ============================================================================= describe('ServiceManager form validation', function () { it('validates required name', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', '') ->call('save') ->assertHasErrors(['name']); }); it('validates name max length', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', str_repeat('A', 101)) ->call('save') ->assertHasErrors(['name']); }); it('validates tagline max length', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('tagline', str_repeat('A', 201)) ->call('save') ->assertHasErrors(['tagline']); }); it('validates description max length', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('description', str_repeat('A', 2001)) ->call('save') ->assertHasErrors(['description']); }); it('validates marketing_url format', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('marketing_url', 'not-a-url') ->call('save') ->assertHasErrors(['marketing_url']); }); it('validates docs_url format', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('docs_url', 'invalid') ->call('save') ->assertHasErrors(['docs_url']); }); it('validates sort_order range', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('sort_order', 1000) ->call('save') ->assertHasErrors(['sort_order']); }); it('validates sort_order minimum', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('sort_order', -1) ->call('save') ->assertHasErrors(['sort_order']); }); it('accepts valid form data', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', 'Updated Social') ->set('tagline', 'New tagline') ->set('marketing_url', 'https://new.example.com') ->set('docs_url', 'https://docs.example.com') ->set('sort_order', 5) ->call('save') ->assertHasNoErrors(); }); it('accepts nullable optional fields', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('tagline', '') ->set('description', '') ->set('icon', '') ->set('color', '') ->set('marketing_domain', '') ->set('marketing_url', '') ->set('docs_url', '') ->call('save') ->assertHasNoErrors(['tagline', 'description', 'icon', 'color', 'marketing_domain', 'marketing_url', 'docs_url']); }); }); // ============================================================================= // Save and Update Tests // ============================================================================= describe('ServiceManager save', function () { it('updates service data on save', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', 'Social Pro') ->set('is_featured', false) ->call('save') ->assertHasNoErrors() ->assertSet('showModal', false) ->assertSet('serviceData', fn ($data) => $data[0]['name'] === 'Social Pro' && $data[0]['is_featured'] === false); }); it('records success message on save', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->call('save') ->assertSee('Service updated successfully.'); }); it('closes modal after successful save', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->call('save') ->assertSet('showModal', false) ->assertSet('editingId', null); }); it('preserves other services when updating one', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', 'Updated Name') ->call('save') ->assertSet('serviceData', fn ($data) => $data[1]['name'] === 'Analytics' && $data[2]['name'] === 'Legacy API'); }); }); // ============================================================================= // Toggle Enabled Tests // ============================================================================= describe('ServiceManager toggle enabled', function () { it('disables an enabled service', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('toggleEnabled', 1) ->assertSet('serviceData', fn ($data) => $data[0]['is_enabled'] === false) ->assertSee('Social has been disabled.'); }); it('enables a disabled service', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('toggleEnabled', 3) ->assertSet('serviceData', fn ($data) => $data[2]['is_enabled'] === true) ->assertSee('Legacy API has been enabled.'); }); it('toggles without opening the modal', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('toggleEnabled', 1) ->assertSet('showModal', false); }); it('can toggle the same service twice', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('toggleEnabled', 1) ->assertSet('serviceData', fn ($data) => $data[0]['is_enabled'] === false) ->call('toggleEnabled', 1) ->assertSet('serviceData', fn ($data) => $data[0]['is_enabled'] === true); }); it('updates enabled count after toggling', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSee('Enabled: 2') ->call('toggleEnabled', 1) ->assertSee('Enabled: 1'); }); }); // ============================================================================= // Sync From Modules Tests // ============================================================================= describe('ServiceManager sync from modules', function () { it('records sync success message', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('syncFromModules') ->assertSee('Services synced from modules successfully.'); }); }); // ============================================================================= // Table Structure Tests // ============================================================================= describe('ServiceManager table structure', function () { it('has correct table columns', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSet(fn ($component) => count($component->tableColumns) === 6 && $component->tableColumns[0] === 'Service' && $component->tableColumns[1] === 'Code' && $component->tableColumns[2] === 'Domain'); }); it('has alignment config for status and actions columns', function () { Livewire::test(ServiceManagerModalDouble::class) ->assertSet(fn ($component) => $component->tableColumns[4]['label'] === 'Status' && $component->tableColumns[4]['align'] === 'center' && $component->tableColumns[5]['label'] === 'Actions' && $component->tableColumns[5]['align'] === 'center'); }); }); // ============================================================================= // Edge Cases // ============================================================================= describe('ServiceManager edge cases', function () { it('handles editing fields while modal is open', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', 'Changed') ->assertSet('name', 'Changed') ->assertSet('editingId', 1) // Still editing same service ->assertSet('code', 'social'); // Read-only unchanged }); it('handles rapid open/close without saving', function () { $component = Livewire::test(ServiceManagerModalDouble::class); for ($i = 0; $i < 5; $i++) { $component->call('openEdit', 1) ->assertSet('showModal', true) ->call('closeModal') ->assertSet('showModal', false); } // Original data should be unchanged $component->assertSet('serviceData', fn ($data) => $data[0]['name'] === 'Social'); }); it('preserves service data across multiple edits', function () { Livewire::test(ServiceManagerModalDouble::class) ->call('openEdit', 1) ->set('name', 'Social v2') ->call('save') ->call('openEdit', 2) ->set('name', 'Analytics v2') ->call('save') ->assertSet('serviceData', fn ($data) => $data[0]['name'] === 'Social v2' && $data[1]['name'] === 'Analytics v2' && $data[2]['name'] === 'Legacy API'); }); });