set('intune_permissions.permissions', [ [ 'key' => 'DeviceManagementApps.Read.All', 'type' => 'application', 'description' => 'Backup application permission', 'features' => ['backup'], ], [ 'key' => 'Group.Read.All', 'type' => 'delegated', 'description' => 'Backup delegated permission', 'features' => ['backup'], ], [ 'key' => 'Reports.Read.All', 'type' => 'application', 'description' => 'Reporting permission', 'features' => ['reporting'], ], ]); config()->set('entra_permissions.permissions', []); TenantPermission::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'permission_key' => 'Group.Read.All', 'status' => 'missing', 'details' => ['source' => 'fixture'], 'last_checked_at' => now(), ]); TenantPermission::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'permission_key' => 'Reports.Read.All', 'status' => 'granted', 'details' => ['source' => 'fixture'], 'last_checked_at' => now(), ]); } function tenantRequiredPermissionsComponent(User $user, Tenant $tenant, array $query = []) { test()->actingAs($user); setAdminPanelContext(); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); $query = array_merge([ 'tenant' => (string) $tenant->external_id, ], $query); return Livewire::withQueryParams($query)->test(TenantRequiredPermissions::class); } it('uses native table filters and search while keeping summary state aligned with visible rows', function (): void { [$user, $tenant] = createUserWithTenant(role: 'readonly'); seedTenantRequiredPermissionsFixture($tenant); $component = tenantRequiredPermissionsComponent($user, $tenant) ->assertTableFilterExists('status') ->assertTableFilterExists('type') ->assertTableFilterExists('features') ->assertCanSeeTableRecords([ 'DeviceManagementApps.Read.All', 'Group.Read.All', ]) ->assertCanNotSeeTableRecords(['Reports.Read.All']) ->assertSee('Missing application permissions') ->assertSee('Guidance'); $component ->filterTable('status', 'present') ->filterTable('type', 'application') ->searchTable('Reports') ->assertCountTableRecords(1) ->assertCanSeeTableRecords(['Reports.Read.All']) ->assertCanNotSeeTableRecords([ 'DeviceManagementApps.Read.All', 'Group.Read.All', ]); $viewModel = $component->instance()->viewModel(); expect($viewModel['overview']['counts'])->toBe([ 'missing_application' => 0, 'missing_delegated' => 0, 'present' => 1, 'error' => 0, ]) ->and(array_column($viewModel['permissions'], 'key'))->toBe(['Reports.Read.All']) ->and($viewModel['copy']['application'])->toBe('DeviceManagementApps.Read.All'); }); it('keeps copy payloads feature-scoped and shows the native no-matches state', function (): void { [$user, $tenant] = createUserWithTenant(role: 'readonly'); seedTenantRequiredPermissionsFixture($tenant); $component = tenantRequiredPermissionsComponent($user, $tenant) ->set('tableFilters.features.values', ['backup']) ->assertSet('tableFilters.features.values', ['backup']); $viewModel = $component->instance()->viewModel(); expect($viewModel['copy']['application'])->toBe('DeviceManagementApps.Read.All') ->and($viewModel['copy']['delegated'])->toBe('Group.Read.All'); $component ->searchTable('no-such-permission') ->assertCountTableRecords(0) ->assertSee('No matches') ->assertTableEmptyStateActionsExistInOrder(['clear_filters']); });