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');
});
});