feat: overhaul project workspace navigation
linter / quality (pull_request) Successful in 1m28s
tests / ci (8.4) (pull_request) Successful in 1m47s

This commit is contained in:
2026-07-18 17:52:25 +02:00
parent a46695c2eb
commit ff796aa10a
45 changed files with 1790 additions and 995 deletions
@@ -0,0 +1,148 @@
<?php
namespace Tests\Feature\Projects;
use App\Actions\Git\GitProviderKey;
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
use App\Models\BoardColumn;
use App\Models\Project;
use App\Models\Team;
use App\Models\TicketStatus;
use App\Models\User;
use App\Models\UserGitConnection;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class ProjectSettingsControllerTest extends TestCase
{
use RefreshDatabase;
public function test_project_admins_can_open_each_settings_section(): void
{
$user = User::factory()->create();
$project = Project::factory()->create(['key' => 'CORE']);
$team = Team::factory()->create();
app(InitializeProjectTicketMetadata::class)->handle($project);
$boardColumn = BoardColumn::factory()->for($project)->create([
'name' => 'In progress',
]);
TicketStatus::factory()->for($project)->for($boardColumn)->create([
'name' => 'In progress',
]);
$this->syncProjectPermissions($project, $user, ['admin']);
$expectedSectionProps = [
'general' => 'sourceTemplates',
'board' => 'boardColumns',
'metadata' => 'ticketTypes',
'repositories' => 'repositoryConnections',
'notifications' => 'notificationActions',
'access' => 'teams',
];
foreach ($expectedSectionProps as $section => $expectedProp) {
$this->actingAs($user)
->get(route('projects.settings.index', [
'project' => $project,
'section' => $section,
]))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('projects/Settings')
->where('activeSection', $section)
->where('project.id', $project->id)
->has("sectionData.{$expectedProp}"),
);
}
}
public function test_missing_and_invalid_sections_fall_back_to_general(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$this->syncProjectPermissions($project, $user, ['admin']);
foreach ([null, 'unknown'] as $section) {
$parameters = ['project' => $project];
if ($section !== null) {
$parameters['section'] = $section;
}
$this->actingAs($user)
->get(route('projects.settings.index', $parameters))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('projects/Settings')
->where('activeSection', 'general')
->has('sectionData.sourceTemplates'),
);
}
}
public function test_users_without_project_admin_permission_cannot_open_settings(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$this->syncProjectPermissions($project, $user, ['view']);
$this->actingAs($user)
->get(route('projects.settings.index', $project))
->assertForbidden();
}
public function test_repository_providers_are_not_queried_outside_the_repository_section(): void
{
Http::preventStrayRequests();
$user = User::factory()->create();
$project = Project::factory()->create();
UserGitConnection::factory()->for($user)->create([
'base_url' => 'https://git.example.test',
'provider' => GitProviderKey::Gitea,
]);
$this->syncProjectPermissions($project, $user, ['admin']);
$this->actingAs($user)
->get(route('projects.settings.index', [
'project' => $project,
'section' => 'general',
]))
->assertOk();
Http::assertNothingSent();
}
public function test_legacy_settings_urls_redirect_to_the_matching_section(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$this->syncProjectPermissions($project, $user, ['admin']);
$legacyRoutes = [
'projects.settings.general' => 'general',
'projects.settings.board' => 'board',
'projects.settings.metadata' => 'metadata',
'projects.settings.repositories' => 'repositories',
'projects.settings.notifications' => 'notifications',
'projects.access' => 'access',
];
foreach ($legacyRoutes as $legacyRoute => $section) {
$this->actingAs($user)
->get(route($legacyRoute, $project))
->assertRedirect(route('projects.settings.index', [
'project' => $project,
'section' => $section,
]));
}
}
}