validate(); expect($result->hasIssues())->toBeFalse($result->formatForAssertion()); }); it('excludes widgets from action surface discovery scope', function (): void { $classes = array_map( static fn ($component): string => $component->className, ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents(), ); $widgetClasses = array_values(array_filter($classes, static function (string $className): bool { return str_starts_with($className, 'App\\Filament\\Widgets\\'); })); expect($widgetClasses)->toBeEmpty(); }); it('keeps baseline exemptions explicit and does not auto-exempt unknown classes', function (): void { $exemptions = ActionSurfaceExemptions::baseline(); expect($exemptions->hasClass('App\\Filament\\Resources\\ActionSurfaceUnknownResource'))->toBeFalse(); }); it('maps tenant/admin panel scope metadata from discovery sources', function (): void { $components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents()) ->keyBy('className'); $tenantResource = $components->get(\App\Filament\Resources\TenantResource::class); $policyResource = $components->get(\App\Filament\Resources\PolicyResource::class); expect($tenantResource)->not->toBeNull(); expect($tenantResource?->hasPanelScope(ActionSurfacePanelScope::Admin))->toBeTrue(); expect($policyResource)->not->toBeNull(); expect($policyResource?->hasPanelScope(ActionSurfacePanelScope::Tenant))->toBeTrue(); }); it('requires non-empty reasons for every baseline exemption', function (): void { $reasons = ActionSurfaceExemptions::baseline()->all(); foreach ($reasons as $className => $reason) { expect(trim($reason))->not->toBe('', "Baseline exemption reason is empty for {$className}"); } }); it('ensures representative declarations satisfy required slots', function (): void { $profiles = new ActionSurfaceProfileDefinition; $declarations = [ PolicyResource::class => PolicyResource::actionSurfaceDeclaration(), OperationRunResource::class => OperationRunResource::actionSurfaceDeclaration(), VersionsRelationManager::class => VersionsRelationManager::actionSurfaceDeclaration(), ]; foreach ($declarations as $className => $declaration) { foreach ($profiles->requiredSlots($declaration->profile) as $slot) { expect($declaration->slot($slot)) ->not->toBeNull("Missing required slot {$slot->value} in declaration for {$className}"); } } }); it('uses More grouping conventions and exposes empty-state CTA on representative CRUD list', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $livewire = Livewire::test(ListPolicies::class) ->assertTableEmptyStateActionsExistInOrder(['sync']); $table = $livewire->instance()->getTable(); $rowActions = $table->getActions(); $rowGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup); expect($rowGroup)->toBeInstanceOf(ActionGroup::class); expect($rowGroup?->getLabel())->toBe('More'); $primaryRowActionCount = collect($rowActions) ->reject(static fn ($action): bool => $action instanceof ActionGroup) ->count(); expect($primaryRowActionCount)->toBeLessThanOrEqual(2); $bulkActions = $table->getBulkActions(); $bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup); expect($bulkGroup)->toBeInstanceOf(BulkActionGroup::class); expect($bulkGroup?->getLabel())->toBe('More'); }); it('uses canonical tenantless View run links on representative operation links', function (): void { $tenant = Tenant::factory()->create(); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'workspace_id' => $tenant->workspace_id, ]); expect(OperationRunLinks::view($run, $tenant)) ->toBe(route('admin.operations.view', ['run' => (int) $run->getKey()])); }); it('removes lone View buttons and uses clickable rows on the inventory items list', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $item = InventoryItem::factory()->create([ 'tenant_id' => $tenant->getKey(), ]); $livewire = Livewire::test(ListInventoryItems::class); $table = $livewire->instance()->getTable(); expect($table->getActions())->toBeEmpty(); $recordUrl = $table->getRecordUrl($item); expect($recordUrl)->not->toBeNull(); expect($recordUrl)->toBe(InventoryItemResource::getUrl('view', ['record' => $item])); }); it('removes lone View buttons and uses clickable rows on the inventory sync runs list', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $run = InventorySyncRun::factory()->create([ 'tenant_id' => $tenant->getKey(), ]); $livewire = Livewire::test(ListInventorySyncRuns::class); $table = $livewire->instance()->getTable(); expect($table->getActions())->toBeEmpty(); $recordUrl = $table->getRecordUrl($run); expect($recordUrl)->not->toBeNull(); expect($recordUrl)->toBe(InventorySyncRunResource::getUrl('view', ['record' => $run])); }); it('keeps representative operation-start actions observable with actor and scope metadata', function (): void { Queue::fake(); bindFailHardGraphClient(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ListPolicies::class) ->mountAction('sync') ->callMountedAction() ->assertHasNoActionErrors(); Queue::assertPushed(SyncPoliciesJob::class); $run = OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'policy.sync') ->latest('id') ->first(); expect($run)->not->toBeNull(); expect((int) $run?->tenant_id)->toBe((int) $tenant->getKey()); expect((int) $run?->workspace_id)->toBe((int) $tenant->workspace_id); expect((string) $run?->initiator_name)->toBe((string) $user->name); });