create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: $role, workspaceRole: $workspaceRole); test()->actingAs($user); setAdminPanelContext(); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); return [$user, $tenant]; } function myWorkInboxPage(?User $user = null, array $query = []) { if ($user instanceof User) { test()->actingAs($user); } setAdminPanelContext(); $factory = $query === [] ? Livewire::actingAs(auth()->user()) : Livewire::withQueryParams($query)->actingAs(auth()->user()); return $factory->test(MyFindingsInbox::class); } function makeAssignedFindingForInbox(Tenant $tenant, User $assignee, array $attributes = []): Finding { return Finding::factory()->for($tenant)->create(array_merge([ 'workspace_id' => (int) $tenant->workspace_id, 'owner_user_id' => (int) $assignee->getKey(), 'assignee_user_id' => (int) $assignee->getKey(), 'status' => Finding::STATUS_TRIAGED, 'subject_external_id' => fake()->uuid(), ], $attributes)); } it('shows only visible assigned open findings and exposes the fixed filter contract', function (): void { [$user, $tenantA] = myWorkInboxActingUser(); $tenantA->forceFill(['name' => 'Alpha Tenant'])->save(); $tenantB = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantA->workspace_id, 'name' => 'Tenant Bravo', ]); createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly'); $hiddenTenant = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantA->workspace_id, 'name' => 'Hidden Tenant', ]); $otherAssignee = User::factory()->create(); createUserWithTenant($tenantA, $otherAssignee, role: 'operator', workspaceRole: 'readonly'); $otherOwner = User::factory()->create(); createUserWithTenant($tenantB, $otherOwner, role: 'owner', workspaceRole: 'readonly'); $assignedVisible = makeAssignedFindingForInbox($tenantA, $user, [ 'subject_external_id' => 'visible-a', 'severity' => Finding::SEVERITY_HIGH, 'status' => Finding::STATUS_NEW, ]); $assignedOwnerSplit = makeAssignedFindingForInbox($tenantB, $user, [ 'subject_external_id' => 'visible-b', 'owner_user_id' => (int) $otherOwner->getKey(), 'status' => Finding::STATUS_IN_PROGRESS, 'due_at' => now()->subDay(), ]); $ownerOnly = makeAssignedFindingForInbox($tenantA, $otherAssignee, [ 'subject_external_id' => 'owner-only', 'owner_user_id' => (int) $user->getKey(), ]); $assignedTerminal = makeAssignedFindingForInbox($tenantA, $user, [ 'subject_external_id' => 'terminal', 'status' => Finding::STATUS_RESOLVED, 'resolved_at' => now(), ]); $assignedToOther = makeAssignedFindingForInbox($tenantA, $otherAssignee, [ 'subject_external_id' => 'other-assignee', ]); $hiddenAssigned = makeAssignedFindingForInbox($hiddenTenant, $user, [ 'subject_external_id' => 'hidden', ]); $component = myWorkInboxPage($user) ->assertCanSeeTableRecords([$assignedVisible, $assignedOwnerSplit]) ->assertCanNotSeeTableRecords([$ownerOnly, $assignedTerminal, $assignedToOther, $hiddenAssigned]) ->assertSee('Owner: '.$otherOwner->name) ->assertSee('Assigned to me only'); expect($component->instance()->summaryCounts())->toBe([ 'open_assigned' => 2, 'overdue_assigned' => 1, ]); expect($component->instance()->availableFilters())->toBe([ [ 'key' => 'assignee_scope', 'label' => 'Assigned to me', 'fixed' => true, 'options' => [], ], [ 'key' => 'tenant', 'label' => 'Tenant', 'fixed' => false, 'options' => [ ['value' => (string) $tenantA->getKey(), 'label' => 'Alpha Tenant'], ['value' => (string) $tenantB->getKey(), 'label' => $tenantB->name], ], ], [ 'key' => 'overdue', 'label' => 'Overdue', 'fixed' => false, 'options' => [], ], [ 'key' => 'reopened', 'label' => 'Reopened', 'fixed' => false, 'options' => [], ], [ 'key' => 'high_severity', 'label' => 'High severity', 'fixed' => false, 'options' => [], ], ]); }); it('defaults to the active tenant prefilter and lets the operator clear it without dropping personal scope', function (): void { [$user, $tenantA] = myWorkInboxActingUser(); $tenantB = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantA->workspace_id, 'name' => 'Beta Tenant', ]); createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly'); $findingA = makeAssignedFindingForInbox($tenantA, $user, [ 'subject_external_id' => 'tenant-a', 'status' => Finding::STATUS_NEW, ]); $findingB = makeAssignedFindingForInbox($tenantB, $user, [ 'subject_external_id' => 'tenant-b', 'status' => Finding::STATUS_TRIAGED, ]); session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [ (string) $tenantA->workspace_id => (int) $tenantB->getKey(), ]); $component = myWorkInboxPage($user) ->assertSet('tableFilters.tenant_id.value', (string) $tenantB->getKey()) ->assertCanSeeTableRecords([$findingB]) ->assertCanNotSeeTableRecords([$findingA]) ->assertActionVisible('clear_tenant_filter'); expect($component->instance()->appliedScope())->toBe([ 'workspace_scoped' => true, 'assignee_scope' => 'current_user_only', 'tenant_prefilter_source' => 'active_tenant_context', 'tenant_label' => $tenantB->name, ]); $component->callAction('clear_tenant_filter') ->assertCanSeeTableRecords([$findingA, $findingB]); expect($component->instance()->appliedScope())->toBe([ 'workspace_scoped' => true, 'assignee_scope' => 'current_user_only', 'tenant_prefilter_source' => 'none', 'tenant_label' => null, ]); }); it('orders overdue work before reopened work and keeps deterministic due-date and id tie breaks', function (): void { [$user, $tenant] = myWorkInboxActingUser(); $overdue = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'overdue', 'status' => Finding::STATUS_IN_PROGRESS, 'due_at' => now()->subDay(), ]); $reopened = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'reopened', 'status' => Finding::STATUS_REOPENED, 'reopened_at' => now()->subHours(6), 'due_at' => now()->addDay(), ]); $ordinarySooner = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'ordinary-sooner', 'status' => Finding::STATUS_TRIAGED, 'due_at' => now()->addDays(2), ]); $ordinaryLater = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'ordinary-later', 'status' => Finding::STATUS_NEW, 'due_at' => now()->addDays(4), ]); $ordinaryNoDue = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'ordinary-no-due', 'status' => Finding::STATUS_TRIAGED, 'due_at' => null, ]); myWorkInboxPage($user) ->assertCanSeeTableRecords([$overdue, $reopened, $ordinarySooner, $ordinaryLater, $ordinaryNoDue], inOrder: true) ->assertSee('Reopened'); }); it('applies reopened and high-severity filters without widening beyond assigned work', function (): void { [$user, $tenant] = myWorkInboxActingUser(); $reopenedHigh = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'reopened-high', 'status' => Finding::STATUS_REOPENED, 'reopened_at' => now()->subHour(), 'severity' => Finding::SEVERITY_CRITICAL, ]); $highOnly = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'high-only', 'status' => Finding::STATUS_TRIAGED, 'severity' => Finding::SEVERITY_HIGH, ]); $ordinary = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'ordinary', 'status' => Finding::STATUS_NEW, 'severity' => Finding::SEVERITY_MEDIUM, ]); myWorkInboxPage($user) ->set('tableFilters.reopened.isActive', true) ->assertCanSeeTableRecords([$reopenedHigh]) ->assertCanNotSeeTableRecords([$highOnly, $ordinary]) ->set('tableFilters.reopened.isActive', false) ->set('tableFilters.high_severity.isActive', true) ->assertCanSeeTableRecords([$reopenedHigh, $highOnly]) ->assertCanNotSeeTableRecords([$ordinary]); }); it('renders the tenant-prefilter empty-state branch and offers only a clear-filter recovery action', function (): void { [$user, $tenantA] = myWorkInboxActingUser(); $tenantB = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantA->workspace_id, 'name' => 'Work Tenant', ]); createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly'); makeAssignedFindingForInbox($tenantB, $user, [ 'subject_external_id' => 'available-elsewhere', ]); $component = myWorkInboxPage($user, [ 'tenant' => (string) $tenantA->external_id, ]) ->assertCanNotSeeTableRecords([]) ->assertSee('No assigned findings match this tenant scope') ->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']); expect($component->instance()->summaryCounts())->toBe([ 'open_assigned' => 0, 'overdue_assigned' => 0, ]); }); it('renders the calm zero-work branch and points back to tenant selection when no active tenant context exists', function (): void { [$user, $tenant] = myWorkInboxActingUser(); session()->forget(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY); Filament::setTenant(null, true); myWorkInboxPage($user) ->assertSee('No visible assigned findings right now') ->assertTableEmptyStateActionsExistInOrder(['choose_tenant_empty']); }); it('uses the active visible tenant for the calm empty-state drillback when tenant context exists', function (): void { [$user, $tenant] = myWorkInboxActingUser(); session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [ (string) $tenant->workspace_id => (int) $tenant->getKey(), ]); $component = myWorkInboxPage($user) ->assertSee('No visible assigned findings right now') ->assertTableEmptyStateActionsExistInOrder(['open_tenant_findings_empty']); expect($component->instance()->emptyState())->toMatchArray([ 'action_name' => 'open_tenant_findings_empty', 'action_label' => 'Open tenant findings', 'action_kind' => 'url', 'action_url' => FindingResource::getUrl('index', panel: 'tenant', tenant: $tenant), ]); }); it('builds tenant detail drilldowns with inbox continuity', function (): void { [$user, $tenant] = myWorkInboxActingUser(); $finding = makeAssignedFindingForInbox($tenant, $user, [ 'subject_external_id' => 'continuity', ]); $component = myWorkInboxPage($user); $detailUrl = $component->instance()->getTable()->getRecordUrl($finding); expect($detailUrl)->toContain(FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant)) ->and($detailUrl)->toContain('nav%5Bback_label%5D=Back+to+my+findings'); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get($detailUrl) ->assertOk() ->assertSee('Back to my findings'); });