user = User::factory()->create(); $this->user->givePermissionTo(['view issues', 'create issues', 'edit issues', 'delete issues', 'upload media']); $this->assignee = User::factory()->create(); $this->project = Project::create([ 'reference' => 'ENH-1', 'name' => 'Proyecto Enh', 'address' => 'Calle 1', 'lat' => 40.0, 'lng' => -3.0, 'start_date' => now()->toDateString(), 'end_date_estimated' => now()->addMonths(3)->toDateString(), 'status' => 'in_progress', 'created_by' => $this->user->id, ]); $this->project->users()->attach($this->user->id, ['role_in_project' => 'supervisor']); $this->project->users()->attach($this->assignee->id, ['role_in_project' => 'worker']); } private function makeIssue(array $attrs = []): Issue { return Issue::create(array_merge([ 'project_id' => $this->project->id, 'title' => 'Incidencia enh', 'status' => 'open', 'priority' => 'medium', 'reported_by' => $this->user->id, ], $attrs)); } private function makeFeature(): Feature { $phase = Phase::create([ 'project_id' => $this->project->id, 'name' => 'F', 'order' => 1, 'color' => '#000', 'progress_percent' => 0, ]); $layer = Layer::create([ 'project_id' => $this->project->id, 'phase_id' => $phase->id, 'name' => 'L', 'color' => '#111', 'uploaded_by' => $this->user->id, ]); return Feature::create([ 'layer_id' => $layer->id, 'name' => 'Muro norte', 'geometry' => ['type' => 'Point', 'coordinates' => [-3.0, 40.0]], 'progress' => 0, 'status' => 'planned', ]); } // ── Notifications ──────────────────────────────────────────────────────────── public function test_assigning_a_task_notifies_the_assignee(): void { Notification::fake(); $issue = $this->makeIssue(); Livewire::actingAs($this->user) ->test(IssueDetail::class, ['project' => $this->project, 'issue' => $issue]) ->set('newTaskTitle', 'Sanear zona') ->set('newTaskAssignee', $this->assignee->id) ->call('addTask'); Notification::assertSentTo($this->assignee, IssueTaskAssignedNotification::class); } public function test_commenting_notifies_the_assignee(): void { Notification::fake(); $issue = $this->makeIssue(['assigned_to' => $this->assignee->id]); Livewire::actingAs($this->user) ->test(IssueDetail::class, ['project' => $this->project, 'issue' => $issue]) ->set('newComment', 'Revisado en obra') ->call('addComment'); Notification::assertSentTo($this->assignee, IssueCommentedNotification::class); } public function test_resolving_notifies_stakeholders(): void { Notification::fake(); $issue = $this->makeIssue(['assigned_to' => $this->assignee->id]); Livewire::actingAs($this->user) ->test(IssueDetail::class, ['project' => $this->project, 'issue' => $issue]) ->call('verifyResolve'); Notification::assertSentTo($this->assignee, IssueStatusChangedNotification::class); $this->assertEquals('resolved', $issue->fresh()->status); } // ── Checklist templates ──────────────────────────────────────────────────────── public function test_checklist_manager_creates_a_template(): void { Livewire::actingAs($this->user) ->test(IssueChecklistManager::class, ['project' => $this->project]) ->set('name', 'Reparación grieta') ->set('items', ['Picar', 'Sellar', 'Pintar']) ->call('save'); $this->assertDatabaseHas('issue_checklist_templates', [ 'project_id' => $this->project->id, 'name' => 'Reparación grieta', ]); } public function test_applying_a_template_creates_the_tasks(): void { $issue = $this->makeIssue(); $template = IssueChecklistTemplate::create([ 'project_id' => $this->project->id, 'name' => 'Plantilla', 'items' => ['Picar', 'Sellar', 'Pintar'], ]); Livewire::actingAs($this->user) ->test(IssueDetail::class, ['project' => $this->project, 'issue' => $issue]) ->set('applyTemplateId', $template->id) ->call('applyTemplate'); $this->assertEquals(3, $issue->tasks()->count()); $this->assertEqualsCanonicalizing(['Picar', 'Sellar', 'Pintar'], $issue->tasks()->pluck('title')->all()); } // ── Overdue alerts ─────────────────────────────────────────────────────────── public function test_overdue_command_notifies_assignee_once(): void { Notification::fake(); $issue = $this->makeIssue(); $task = $issue->tasks()->create([ 'title' => 'Tarea vencida', 'assigned_to' => $this->assignee->id, 'due_date' => now()->subDays(2)->toDateString(), 'is_done' => false, 'uuid' => (string) \Illuminate\Support\Str::uuid(), ]); $this->artisan('issues:notify-overdue')->assertSuccessful(); $this->artisan('issues:notify-overdue')->assertSuccessful(); // idempotent Notification::assertSentTo($this->assignee, IssueTaskOverdueNotification::class); Notification::assertCount(1); $this->assertNotNull($task->fresh()->overdue_notified_at); } // ── Issue type / category ────────────────────────────────────────────────────── public function test_create_form_shows_the_type_field(): void { $this->actingAs($this->user) ->get(route('projects.issues.create', $this->project)) ->assertOk() ->assertSee('Tipo de incidencia') ->assertSee('Defecto') ->assertSee('Seguridad'); } public function test_editing_an_issue_persists_the_type(): void { $issue = $this->makeIssue(['type' => 'defect']); Livewire::actingAs($this->user) ->test(IssueForm::class, ['project' => $this->project, 'issue' => $issue]) ->assertSet('type', 'defect') ->set('type', 'safety') ->call('save'); $this->assertEquals('safety', $issue->fresh()->type); } // ── Report issue from a map feature ────────────────────────────────────────── public function test_issue_form_prelinks_feature_from_query(): void { $feature = $this->makeFeature(); // The create form mounts during the GET request, where it reads ?feature=. $this->actingAs($this->user) ->get(route('projects.issues.create', ['project' => $this->project, 'feature' => $feature->id])) ->assertOk() ->assertSee('Muro norte') ->assertSee('Vinculada al elemento del mapa'); } }