create(); $project = Project::factory()->create([ 'key' => 'CORE', ]); app(InitializeProjectTicketMetadata::class)->handle($project); BoardColumn::factory()->for($project)->create([ 'name' => 'Todo', 'position' => 1, ]); $this->syncProjectPermissions($project, $user, ['edit']); $this->actingAs($user) ->get(route('projects.settings.board', $project)) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('projects/settings/Board') ->where('project.id', $project->id) ->where('backlogTicketStatuses.0.name', 'Backlog') ->where('boardColumns.0.name', 'Todo'), ); } /** * Ensure users without edit permission cannot view board settings. */ public function test_users_without_edit_permission_cannot_view_board_settings(): void { $user = User::factory()->create(); $project = Project::factory()->create(); $this->actingAs($user) ->get(route('projects.settings.board', $project)) ->assertForbidden(); } /** * Ensure authorized users can create board columns. */ public function test_users_with_edit_permission_can_create_board_columns(): void { $user = User::factory()->create(); $project = Project::factory()->create([ 'key' => 'CORE', ]); $this->syncProjectPermissions($project, $user, ['edit']); $this->actingAs($user) ->post(route('projects.settings.board.columns.store', $project), [ 'name' => 'In Review', ]) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHas('status', 'In Review was added to the board workflow.'); $this->assertDatabaseHas('board_columns', [ 'project_id' => $project->id, 'name' => 'In Review', 'position' => 1, ]); $this->assertDatabaseHas('ticket_statuses', [ 'project_id' => $project->id, 'name' => 'In Review', 'slug' => 'in-review', 'position' => 1, ]); } /** * Ensure authorized users can rename and reorder board columns. */ public function test_users_with_edit_permission_can_update_and_reorder_board_columns(): void { $user = User::factory()->create(); $project = Project::factory()->create(); $firstColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'Todo', 'position' => 1, ]); $secondColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'In Progress', 'position' => 2, ]); $thirdColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'Done', 'position' => 3, ]); $this->syncProjectPermissions($project, $user, ['edit']); $this->actingAs($user) ->patch(route('projects.settings.board.columns.update', [$project, $thirdColumn]), [ 'name' => 'Ready for QA', 'position' => 1, ]) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHas('status', 'Ready for QA was updated.'); $firstColumn->refresh(); $secondColumn->refresh(); $thirdColumn->refresh(); $this->assertSame(2, $firstColumn->position); $this->assertSame(3, $secondColumn->position); $this->assertSame(1, $thirdColumn->position); $this->assertSame('Ready for QA', $thirdColumn->name); } /** * Ensure deleting a column normalizes the remaining order. */ public function test_deleting_a_board_column_normalizes_remaining_positions(): void { $user = User::factory()->create(); $project = Project::factory()->create(); $firstColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'Todo', 'position' => 1, ]); $secondColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'In Progress', 'position' => 2, ]); $thirdColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'Done', 'position' => 3, ]); $this->syncProjectPermissions($project, $user, ['edit']); $this->actingAs($user) ->delete(route('projects.settings.board.columns.destroy', [$project, $secondColumn])) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHas('status', 'In Progress was removed from the board workflow.'); $firstColumn->refresh(); $thirdColumn->refresh(); $this->assertSame(1, $firstColumn->position); $this->assertSame(2, $thirdColumn->position); $this->assertDatabaseMissing('board_columns', [ 'id' => $secondColumn->id, ]); } /** * Ensure authorized users can manage multiple statuses for backlog and columns. */ public function test_users_with_edit_permission_can_manage_ticket_statuses(): void { $user = User::factory()->create(); $project = Project::factory()->create([ 'key' => 'CORE', ]); $this->syncProjectPermissions($project, $user, ['edit']); $boardColumn = BoardColumn::factory()->for($project)->create([ 'name' => 'Todo', 'position' => 1, ]); $defaultStatus = TicketStatus::factory() ->for($project) ->for($boardColumn) ->create([ 'name' => 'Todo', 'position' => 1, ]); $this->actingAs($user) ->post(route('projects.settings.board.statuses.store', $project), [ 'board_column_id' => $boardColumn->id, 'name' => 'Ready for QA', ]) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHas('status', 'Ready for QA was added to Todo.'); $newStatus = TicketStatus::query() ->where('project_id', $project->id) ->where('name', 'Ready for QA') ->firstOrFail(); $this->assertSame($boardColumn->id, $newStatus->board_column_id); $this->assertSame('ready-for-qa', $newStatus->slug); $this->assertTrue($defaultStatus->allowedTargetStatuses()->whereKey($newStatus->id)->exists()); $this->actingAs($user) ->patch(route('projects.settings.board.statuses.update', [$project, $newStatus]), [ 'name' => 'QA Ready', ]) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHas('status', 'QA Ready was updated.'); $newStatus->refresh(); $this->assertSame('QA Ready', $newStatus->name); $this->assertSame('qa-ready', $newStatus->slug); $ticket = Ticket::factory()->create([ 'project_id' => $project->id, 'ticket_status_id' => $newStatus->id, ]); $this->actingAs($user) ->delete(route('projects.settings.board.statuses.destroy', [$project, $newStatus])) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHas('status', 'QA Ready was removed from Todo.'); $this->assertDatabaseMissing('ticket_statuses', [ 'id' => $newStatus->id, ]); $this->assertDatabaseHas('tickets', [ 'id' => $ticket->id, 'ticket_status_id' => $defaultStatus->id, ]); } /** * Ensure the last status in a column or backlog cannot be deleted. */ public function test_last_ticket_status_for_a_context_cannot_be_deleted(): void { $user = User::factory()->create(); $project = Project::factory()->create(); $this->syncProjectPermissions($project, $user, ['edit']); $backlogStatus = TicketStatus::factory() ->for($project) ->create([ 'name' => 'Backlog', 'position' => 1, ]); $this->actingAs($user) ->from(route('projects.settings.board', $project)) ->delete(route('projects.settings.board.statuses.destroy', [$project, $backlogStatus])) ->assertRedirect(route('projects.settings.board', $project)) ->assertSessionHasErrors('ticket_status_id'); $this->assertDatabaseHas('ticket_statuses', [ 'id' => $backlogStatus->id, ]); } }