feat(KAN-19): add responsive permission-aware navigation
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NavigationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
#[DataProvider('projectRoles')]
|
||||
public function test_navigation_uses_effective_project_permissions(string $role, bool $teamAccess, bool $canManage, bool $canCreateTickets): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
$assignee = $user;
|
||||
if ($teamAccess) {
|
||||
$assignee = Team::factory()->create();
|
||||
$assignee->users()->attach($user);
|
||||
}
|
||||
$this->syncProjectPermissions($project, $assignee, [$role]);
|
||||
|
||||
$this->actingAs($user)->get(route('projects.board.index', $project))
|
||||
->assertOk()->assertInertia(fn (Assert $page) => $page
|
||||
->where('navigation.project', [
|
||||
'id' => $project->id,
|
||||
'name' => $project->name,
|
||||
'key' => $project->key,
|
||||
'canManage' => $canManage,
|
||||
'canCreateTickets' => $canCreateTickets,
|
||||
]));
|
||||
}
|
||||
|
||||
/** @return array<string, array{string, bool, bool, bool}> */
|
||||
public static function projectRoles(): array
|
||||
{
|
||||
return [
|
||||
'reader' => ['read', false, false, false],
|
||||
'writer' => ['write', false, false, true],
|
||||
'project admin' => ['admin', false, true, true],
|
||||
'team reader' => ['read', true, false, false],
|
||||
'team writer' => ['write', true, false, true],
|
||||
'team admin' => ['admin', true, true, true],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_global_admin_has_project_actions_without_direct_membership(): void
|
||||
{
|
||||
$user = User::factory()->admin()->create();
|
||||
$project = Project::factory()->create();
|
||||
$this->actingAs($user)->get(route('projects.show', $project))
|
||||
->assertOk()->assertInertia(fn (Assert $page) => $page
|
||||
->where('navigation.project.canManage', true)
|
||||
->where('navigation.project.canCreateTickets', true)
|
||||
->where('auth.permissions.globalAdmin', true));
|
||||
}
|
||||
|
||||
public function test_every_project_admin_navigation_destination_is_authorized(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
$this->syncProjectPermissions($project, $user, ['admin']);
|
||||
foreach (['projects.show', 'projects.board.index', 'projects.backlog.index', 'projects.settings.general', 'projects.settings.board', 'projects.settings.metadata', 'projects.settings.repositories', 'projects.settings.notifications', 'projects.access'] as $route) {
|
||||
$this->actingAs($user)->get(route($route, $project))
|
||||
->assertOk()->assertInertia(fn (Assert $page) => $page
|
||||
->where('navigation.project.id', $project->id)
|
||||
->where('navigation.project.canManage', true)
|
||||
->where('auth.permissions.globalAdmin', false));
|
||||
}
|
||||
}
|
||||
|
||||
public function test_project_context_is_cleared_when_leaving_a_project(): void
|
||||
{
|
||||
$user = User::factory()->admin()->create();
|
||||
$project = Project::factory()->create();
|
||||
$this->actingAs($user)->get(route('projects.show', $project))
|
||||
->assertInertia(fn (Assert $page) => $page->where('navigation.project.id', $project->id));
|
||||
$this->get(route('dashboard'))->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page->where('navigation.project', null));
|
||||
$this->get(route('projects.create'))->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page->where('navigation.project', null));
|
||||
}
|
||||
|
||||
public function test_unrelated_projects_remain_inaccessible(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create(['name' => 'Private project']);
|
||||
$this->actingAs($user)->get(route('projects.show', $project))
|
||||
->assertForbidden()->assertDontSee('Private project');
|
||||
}
|
||||
|
||||
public function test_navigation_rendering_and_url_matching(): void
|
||||
{
|
||||
$process = new Process(['node', 'tests/Fixtures/navigation.mjs'], base_path());
|
||||
$process->setTimeout(60);
|
||||
$process->run();
|
||||
$this->assertTrue($process->isSuccessful(), $process->getOutput().$process->getErrorOutput());
|
||||
$this->assertStringContainsString('Navigation behavior checks passed.', $process->getOutput());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user